Strand Design Language
Install
npm install @dillingerstaffing/strand @dillingerstaffing/strand-ui
npm install @dillingerstaffing/strand @dillingerstaffing/strand-vue
npm install @dillingerstaffing/strand @dillingerstaffing/strand-svelte
npm install @dillingerstaffing/strand @dillingerstaffing/strand-ui
Use CSS classes directly. HTML Reference
npm install @dillingerstaffing/strand @dillingerstaffing/strand-ui bulma
Then import @dillingerstaffing/strand/bulma/strand-bulma-compat.css after Bulma. Migration guide
Or run npx strand-ui init to auto-detect your framework. CDN also available.
Features
Input, display, layout, navigation, feedback, surface, and animation. Every interaction state.
All tokens are CSS custom properties. No ThemeProvider. No CSS-in-JS. No runtime cost.
WCAG 2.2 AA. Every color pairing passes contrast. WAI-ARIA compliant. Reduced motion.
Total gzipped. Static CSS. Tree-shakeable. Framework-agnostic tokens.
What are you building?
Strand UI Showcases
Check back soon.
Get running in three steps
Install
npm install @dillingerstaffing/strand @dillingerstaffing/strand-uiImport CSS
@import '@dillingerstaffing/strand/css/reset.css';
@import '@dillingerstaffing/strand/css/tokens.css';
@import '@dillingerstaffing/strand/css/base.css';
@import '@dillingerstaffing/strand-ui/css/strand-ui.css';Components are unstyled without these imports.
Use
import { Button, Card, Stack } from '@dillingerstaffing/strand-ui';
<Card variant="elevated">
<Stack gap={4}>
<Button>Get Started</Button>
</Stack>
</Card>Preact: Works natively, no configuration needed.
React: Alias preact in your bundler; see framework setup.
Vue 3: Use strand-vue; components register as native Vue SFCs.
Svelte: Use strand-svelte; components register as native Svelte components.
CSS Only: Use classes directly per HTML Reference.
Bulma: Load the Strand Bulma theme for visual cohesion.
Every component, rendered
Input
Button
Primary action trigger. 4 variants, 3 sizes, loading state.
| Prop | Type | Default |
|---|---|---|
variant | "primary" | "secondary" | "ghost" | "danger" | "primary" |
size | "sm" | "md" | "lg" | "md" |
loading | boolean | false |
iconOnly | boolean | false |
fullWidth | boolean | false |
disabled | boolean | false |
<Button variant="primary" size="md">Submit</Button>Input
Single-line text entry. 5 types, leading/trailing addons, error state.
| Prop | Type | Default |
|---|---|---|
type | "text" | "email" | "password" | "search" | "number" | "text" |
error | boolean | false |
leadingAddon | ReactNode | - |
trailingAddon | ReactNode | - |
disabled | boolean | false |
<Input type="email" placeholder="[email protected]" />Textarea
Multi-line text entry. Auto-resize, character count, error state.
| Prop | Type | Default |
|---|---|---|
autoResize | boolean | false |
showCount | boolean | false |
maxLength | number | - |
error | boolean | false |
<Textarea autoResize placeholder="Message" />Select
Option selection. Native select with custom styling and error state.
| Prop | Type | Default |
|---|---|---|
options | SelectOption[] | [] |
value | string | - |
error | boolean | false |
placeholder | string | - |
<Select options={items} onChange={handleChange} />Checkbox
Binary toggle for multiple selections. Supports indeterminate state.
| Prop | Type | Default |
|---|---|---|
checked | boolean | false |
indeterminate | boolean | false |
label | string | - |
disabled | boolean | false |
<Checkbox checked={value} onChange={fn} label="Accept terms" />Radio
Single selection from a set. Group with shared name attribute.
| Prop | Type | Default |
|---|---|---|
checked | boolean | false |
name | string | - |
value | string | - |
label | string | - |
<Radio name="plan" value="pro" label="Pro plan" />Switch
Binary toggle for a single setting. On/off with inline label.
| Prop | Type | Default |
|---|---|---|
checked | boolean | false |
label | string | - |
onChange | (checked: boolean) => void | - |
<Switch checked={enabled} onChange={setEnabled} label="Notifications" />Slider
Range value selection with single thumb.
| Prop | Type | Default |
|---|---|---|
min | number | 0 |
max | number | 100 |
step | number | 1 |
value | number | 50 |
<Slider min={0} max={100} value={vol} onChange={setVol} />FormField
Wraps any input with label, hint text, error message, and required indicator.
| Prop | Type | Default |
|---|---|---|
label | string | - |
hint | string | - |
error | string | - |
required | boolean | false |
<FormField label="Email" error={err} required>
<Input type="email" />
</FormField>Display
Card
Content container. Elevated, outlined, or interactive with hover lift.
| Prop | Type | Default |
|---|---|---|
variant | "elevated" | "outlined" | "interactive" | "elevated" |
padding | "none" | "sm" | "md" | "lg" | "md" |
<Card variant="elevated" padding="lg">...</Card>Badge
Status dot or count indicator. 5 status colors.
| Prop | Type | Default |
|---|---|---|
variant | "dot" | "count" | "dot" |
status | "default" | "teal" | "blue" | "amber" | "red" | "default" |
count | number | - |
<Badge variant="count" status="red" count={notifications} />Avatar
User or entity representation. Image, initials fallback. 4 sizes.
| Prop | Type | Default |
|---|---|---|
src | string | - |
initials | string | - |
size | "sm" | "md" | "lg" | "xl" | "md" |
<Avatar src="/photo.jpg" alt="Jane" size="lg" />Tag
Categorization label. Solid or outlined, removable, 5 status colors.
| Prop | Type | Default |
|---|---|---|
variant | "solid" | "outlined" | "solid" |
status | "default" | "teal" | "blue" | "amber" | "red" | "default" |
removable | boolean | false |
<Tag status="blue" removable onRemove={fn}>React</Tag>Table
Tabular data display. Sortable headers, responsive horizontal scroll.
| Prop | Type | Default |
|---|---|---|
columns | TableColumn[] | - |
data | T[] | - |
onSort | (key, direction) => void | - |
<Table columns={cols} data={rows} onSort={handleSort} />DataReadout
Monospace instrument readout. Overline label + large value display.
| Prop | Type | Default |
|---|---|---|
label | string | - |
value | string | number | - |
size | "sm" | "md" | "lg" | "md" |
<DataReadout label="Revenue" value="$94,200" />Layout
Stack
Flex layout primitive. Vertical or horizontal stacking with gap control.
| Prop | Type | Default |
|---|---|---|
direction | "vertical" | "horizontal" | "vertical" |
gap | number | 0 |
align | "start" | "center" | "end" | "stretch" | "stretch" |
wrap | boolean | false |
<Stack direction="horizontal" gap={4} align="center">...</Stack>Grid
CSS grid layout primitive. Column count and gap control.
| Prop | Type | Default |
|---|---|---|
columns | number | 1 |
gap | number | 0 |
<Grid columns={3} gap={6}>...</Grid>Container
Width constraint. 4 tiers: narrow (640px), default (768px), wide (1024px), full (1280px).
| Prop | Type | Default |
|---|---|---|
size | "narrow" | "default" | "wide" | "full" | "default" |
<Container size="wide">...</Container>Divider
Visual separator. Horizontal or vertical, with optional label.
| Prop | Type | Default |
|---|---|---|
direction | "horizontal" | "vertical" | "horizontal" |
label | string | - |
<Divider label="or" />Section
Page section with consistent padding rhythm. Standard or hero variant.
| Prop | Type | Default |
|---|---|---|
variant | "standard" | "hero" | "standard" |
background | "primary" | "elevated" | "recessed" | "primary" |
<Section variant="hero" background="elevated">...</Section>Navigation
Link
Inline navigation with underline-grow-from-left hover animation.
| Prop | Type | Default |
|---|---|---|
href | string | - |
external | boolean | false |
<Link href="/about">About us</Link>Tabs
Content switching. Full WAI-ARIA tabs with keyboard arrow navigation.
| Prop | Type | Default |
|---|---|---|
tabs | TabItem[] | - |
activeTab | string | - |
onChange | (id: string) => void | - |
<Tabs tabs={items} activeTab={current} onChange={setTab} />Breadcrumb
Hierarchical location indicator with custom separator.
| Prop | Type | Default |
|---|---|---|
items | BreadcrumbItem[] | - |
separator | string | "/" |
<Breadcrumb items={[{ label: "Home", href: "/" }, { label: "Page" }]} />Nav
Site or app navigation bar. Responsive hamburger collapse on mobile.
| Prop | Type | Default |
|---|---|---|
logo | ReactNode | - |
items | NavItem[] | - |
actions | ReactNode | - |
<Nav logo={<Logo />} items={navItems}>
<Button size="sm">Sign In</Button>
</Nav>Feedback
Toast
Transient notification. 4 statuses. Auto-dismiss. Use with ToastProvider + useToast().
| Prop | Type | Default |
|---|---|---|
status | "info" | "success" | "warning" | "error" | "info" |
message | string | - |
duration | number (ms) | 5000 |
const { toast } = useToast();
toast({ message: "Saved!", status: "success" });Alert
Persistent inline notification. 4 statuses, optional dismiss.
| Prop | Type | Default |
|---|---|---|
status | "info" | "success" | "warning" | "error" | "info" |
dismissible | boolean | false |
onDismiss | () => void | - |
<Alert status="success" dismissible onDismiss={fn}>Saved!</Alert>Dialog
Modal overlay. Focus trap, escape-to-close, portal rendering, scroll lock.
| Prop | Type | Default |
|---|---|---|
open | boolean | false |
onClose | () => void | - |
title | string | - |
closeOnEscape | boolean | true |
<Dialog open={show} onClose={close} title="Confirm">
Are you sure?
</Dialog>Tooltip
Contextual hint on hover/focus. 4 positions, configurable delay.
| Prop | Type | Default |
|---|---|---|
content | string | - |
position | "top" | "right" | "bottom" | "left" | "top" |
delay | number (ms) | 200 |
<Tooltip content="More info" position="top">
<Button>Hover me</Button>
</Tooltip>Progress
Completion indicator. Bar or ring variant, determinate or indeterminate.
| Prop | Type | Default |
|---|---|---|
variant | "bar" | "ring" | "bar" |
value | number (0-100) | - (indeterminate) |
size | "sm" | "md" | "lg" | "md" |
<Progress variant="ring" value={75} />Spinner
Loading indicator. Thin ring animation with screen reader text.
| Prop | Type | Default |
|---|---|---|
size | "sm" | "md" | "lg" | "md" |
<Spinner size="md" />Skeleton
Content placeholder with shimmer animation. Text, rectangle, or circle.
| Prop | Type | Default |
|---|---|---|
variant | "text" | "rectangle" | "circle" | "text" |
width | string | "100%" |
height | string | "1em" |
<Skeleton variant="text" width="200px" />
<Skeleton variant="circle" width="40px" height="40px" />The Right Person. The Right Role.
Matches that last, by understanding what actually matters about a role, a team, and a person.