The WhileSmart design system: Vue 3 components, layouts, composables, and design tokens shared by every WhileSmart product surface.
The package is published to GitHub Packages, which requires an authenticated npm client even for public packages. Add an .npmrc next to your package.json:
@whilesmart:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
Set NODE_AUTH_TOKEN to a GitHub token with read:packages, then:
npm install @whilesmart/designvue and vue-router are peer dependencies.
Load the stylesheet once, at the entry point. It carries the tokens, the base reset, and every component style:
import '@whilesmart/design/styles'Then import what you need:
import { DsButton, DsCard, DsCommandPalette } from '@whilesmart/design'
import { DashboardLayout, PublicLayout } from '@whilesmart/design/layouts'
import { useAppSwitcher, appUrl } from '@whilesmart/design/composables'Components are prefixed Ds. The root entry also re-exports the layouts and composables, so a single import specifier works if you prefer it.
@whilesmart/design/vite provides the shared app shell: a loading state rendered before the Vue app mounts, crawlable markup and llms.txt for public landing pages, and a helper that rewrites Set-Cookie headers for local proxying.
import { whilesmartAppLoader, localizeSetCookie } from '@whilesmart/design/vite'
export default defineConfig({
plugins: [whilesmartAppLoader({ site: { /* ... */ } }), vue()]
})A few components reference product marks by absolute path (/mail-icon.svg, /files-icon.svg, /chat-icon.svg, /desk-icon.svg, /whilesmart-icon.svg), so the host application must serve them from its own public directory. The source SVGs ship with the package for copying:
@whilesmart/design/icons/mail.svg
Icons referenced through the appIcons map and the app switcher are inlined into the bundle and need no copying.
Every colour, space, type step, and shadow is a CSS custom property under the --ds- prefix, defined in src/tokens/. Light values sit on :root; the dark theme redeclares them under [data-theme="dark"]. Individual token files are importable directly:
import '@whilesmart/design/tokens/colors.css'Component styles only ever read tokens, never literal colours. npm run check enforces that text pairs clear a 4.5:1 contrast ratio in both themes.
src/tokens/brand.css lists every token a host may redefine: the primary and secondary
ramps, the sans and mono families, the card radius, the control radius, the table
density, and the boxed console's ground and page fills. Redeclare them on :root in a stylesheet loaded after
@whilesmart/design/styles, and the whole system follows:
:root {
--ds-brand-primary-800: #047844;
/* ... the rest of the ramp ... */
--ds-brand-font-sans: 'Ubuntu', system-ui, sans-serif;
}Set the whole ramp. A partial one leaves the remaining steps on the WhileSmart defaults and shifts hue between steps. Hold your own values to the same 4.5:1 bar: the check here covers the defaults, not yours.
An area colour names a place in the product, never a status. A navigation destination, its icon tile and the accents on its own page all draw from one area, so a reader learns the colour once. A row, a chip or a finding that carries meaning uses the status tokens instead. The two never share a surface.
Each area has a soft fill, an ink that clears 4.5:1 on that fill, and a solid for a
filled tile carrying --ds-area-on-solid. Components take an area prop; areaStyle()
resolves it for anything hand-rolled.
npm install
npm test # unit tests
npm run check # tokens, contrast, icon names, tests, build
npm run build # dist/The build produces ESM bundles, .d.ts declarations emitted by vue-tsc, a flattened dist/design.css, and the raw token and icon sources.
Releases run through the shared js/publish action in whilesmart/workflows, so this repository follows the same flow as every other WhileSmart package.
- Bump
versioninpackage.jsonand add the matching## [version]section toCHANGELOG.md. The release fails without it, because a version published without notes cannot be published again to add them. - Merge that to
main. - Push the tag:
git tag v0.2.0 && git push origin v0.2.0.
The workflow verifies the tag against package.json, runs npm run check, publishes to GitHub Packages, and creates the GitHub release from the changelog entry.
@whilesmart/design/viewer exports DsViewerFrame, DsViewerContent, DsPreviewFrame, DsModal, and createPreviewDocument. Import @whilesmart/design/component-styles for the component styles without importing global tokens or resets.
The document helper supplies the preview stylesheet using the shared design tokens. The frame supplies navigation, loading and error states, and a content slot. The content component displays supplied image, media, text, PDF, or HTML representations. The preview frame isolates supplied HTML in a sandbox without script execution and resizes after images load. HTML must be sanitized or trusted before it reaches the frame; sandboxing does not sanitize content.
These components do not fetch files, convert documents, register renderers, or decide permissions. The calling app or viewing framework supplies content and handles emitted navigation actions.
@whilesmart/design/timelines is a separate entry, so an app that never imports it ships none of its code or styles. Import its styles once:
import { DsArcTimeline } from '@whilesmart/design/timelines'
import '@whilesmart/design/timelines/style.css'Each timeline is named for its shape. DsArcTimeline places moments by time across and by how they landed (valence, -2 to 2) up and down, sizes them by weight, and draws arcs between moments that share a cue. It takes moments and binds focus, period and types with v-model:
<DsArcTimeline v-model:focus="focus" v-model:period="period" :moments="moments" :link-on="['area']" :colors="{ incident: 'rose' }" />A moment is { id, label, type, at, until?, valence, intensity?, importance?, cues?, actors?, icon?, detail? }. One without at or valence is counted but not drawn. Its size reads importance, then intensity; weight picks another field or a function. marks="face" draws each moment as its first actor's face. within draws one period only.
Pass events to draw them as pins by how they landed. Picking one, bound with v-model:event, lights the moments it touches:
<DsArcTimeline v-model:event="picked" :moments="memories" :events="reviews" marks="face" />DsStreamTimeline lays events out as cards along time. With shelves it also gathers the records they touched into shelves underneath, with a thread from each card to each shelf it reached; listed sets how many records each shelf lists on the canvas (0 shows only counts). It pans by dragging, zooms around the pointer, opens on the latest 14 days (opening) and fits everything from its toolbar. Quiet stretches longer than three days are cut out (squeeze, null keeps calendar scale), and threads run at right angles (lines: step, straight or curve). It binds focus, hidden (record types) and shelf (the open shelf), and emits open for an event with parts:
<DsStreamTimeline v-model:focus="focus" :events="events" :records="memories" :group-of="(m) => m.folder" @open="drillInto" />An event is { id, title, at, until?, type?, icon?, place?, actors?, valence?, intensity?, importance?, touches?, parts?, count? } and a record is { id, type, label, group? }. The card, record, shelf and empty slots draw those parts as the page wants; pin and mark do the same on the arc.
The layout functions (placeMoments, layoutStream, packRows, timeTicks, chapters, linksFrom, placeLabels) are exported for building side panels or other timeline shapes on the same scale. usePanZoom gives any scrolling box the same drag and wheel handling.
@whilesmart/design/canvas is a separate entry built on @vue-flow/core, an optional peer dependency that only apps importing the canvas need to install. Its stylesheet includes Vue Flow's base styles.
import { DsCanvas, DsCanvasCard } from '@whilesmart/design/canvas'
import '@whilesmart/design/canvas/style.css'DsCanvas lays cards out in named lanes from the data alone, so the same data draws the same picture on every visit. Pass storageKey to keep cards where a reader dragged them. Lines come from edges, and createEdgeRegistry merges several edge sources so one failing source costs only its own lines.
DsProgress displays a bounded progress bar with an accessible label. Pass
value and an optional max (100 by default). Its tone accepts brand,
success, warning, or danger. Values outside the range are clamped for the
bar; surrounding content can show the original value when it exceeds the limit.