@boostdev/design-system-components
v1.2.1
Published
BoostDev React component library: accessible, token-driven components built on @boostdev/design-system-foundation
Readme
@boostdev/components
Accessible, token-driven React components built on @boostdev/design-system-foundation.
Zero extra runtime — bring your own CSS reset and token layer, then import the components.
Installation
pnpm add @boostdev/components @boostdev/design-system-foundation
npm install @boostdev/components @boostdev/design-system-foundation
yarn add @boostdev/components @boostdev/design-system-foundation@boostdev/design-system-foundation is a required peer dependency. The components rely on its
CSS custom properties for all colour, spacing, and typography values.
Setup
Import the design system tokens and the component styles once at your app root:
/* app.css */
@import "@boostdev/design-system-foundation/css";
@import "@boostdev/components/css";Or with a JS bundler (Vite, Next.js, etc.):
// main.ts / layout.tsx
import '@boostdev/design-system-foundation/css';
import '@boostdev/components/css';All component styles live inside @layer component, which sits above the token layers in the
cascade. You can override any component from outside without specificity fights.
Next.js / React Server Components
The main @boostdev/components entry has no 'use client' directive. When Next.js evaluates the
bundle in a React Server Component context, components that call createContext (e.g. Toast) will
crash at module evaluation time.
Use the /client subpath instead — it is identical in API but has 'use client' prepended to the
bundle, which tells Next.js to treat it as client-only code:
// Plain React / Vite — unchanged
import { Button, Card } from '@boostdev/components';
// Next.js / RSC — use the /client subpath
import { Button, Card } from '@boostdev/components/client';Usage
UI components
Badge
import { Badge } from '@boostdev/components';
<Badge>New</Badge>
<Badge variant="success">Active</Badge>
<Badge variant="error">Failed</Badge>
<Badge variant="warning">Pending</Badge>
<Badge variant="secondary">Draft</Badge>Variants: primary (default) · secondary · success · error · warning
Collapsible
import { Collapsible } from '@boostdev/components';
<Collapsible summary="What is the return policy?" defaultOpen>
You can return any item within 30 days of purchase.
</Collapsible>
{/* Group: only one open at a time */}
<Collapsible name="faq" summary="Question 1">Answer 1</Collapsible>
<Collapsible name="faq" summary="Question 2">Answer 2</Collapsible>Built on native <details>/<summary>. Use name to group multiple Collapsibles so only one stays open at a time.
Typography
import { Typography } from '@boostdev/components';
<Typography variant="h1">Page title</Typography>
<Typography variant="h2">Section title</Typography>
<Typography variant="body">Paragraph text</Typography>
<Typography variant="body_s">Small print</Typography>Pass component to override the rendered element without changing the visual style:
<Typography variant="h2" component="h3">Visually h2, semantically h3</Typography>Variants: h1 · h2 · h3 · body (default) · body_s
Loading
import { Loading } from '@boostdev/components';
<Loading />
<Loading size="small" />
<Loading size="large" />Sizes: small · medium (default) · large
Skeleton
import { Skeleton } from '@boostdev/components';
<Skeleton className="w-48 h-4" />Renders an aria-hidden shimmer block. Size it with a className or inline style.
Interaction components
Button
import { Button } from '@boostdev/components';
<Button>Click me</Button>
<Button variant="ghost">Cancel</Button>
<Button size="small">Small</Button>
<Button href="/dashboard">Link button</Button>
<Button iconStart={<Icon />} aria-label="Delete" />
<Button hasPulse>Call to action</Button>| Prop | Type | Default |
|---|---|---|
| variant | 'default' \| 'ghost' | 'default' |
| size | 'small' \| 'medium' \| 'large' | 'medium' |
| href | string | — renders an <a> |
| iconStart | ReactNode | — |
| iconEnd | ReactNode | — |
| hasPulse | boolean | false |
| disabled | boolean | false |
Variants:
default— filled with--button_color(CTA green). Hover transitions to an outlined state.ghost— transparent background, border and text use--button_color. Hover fills.
Re-theming — set two CSS custom properties to change the colour of both variants:
.danger-zone .my-button {
--button_color: var(--bds-error);
--button_on-color: var(--bds-on-error);
}Dialog
import { Dialog } from '@boostdev/components';
<Dialog isOpen={open} onClose={() => setOpen(false)}>
<h2>Confirm</h2>
<p>Are you sure?</p>
</Dialog>Renders a native <dialog> element with a dimmed, blurred backdrop. isOpen controls open/close;
onClose is called when the close button is pressed or the backdrop is clicked. Dialog traps focus
while open and restores focus to the triggering element on close.
Override the backdrop colour via --color_backdrop (default: rgb(0 0 0 / 50%)).
Rating
import { Rating } from '@boostdev/components';
<Rating value={3} />
<Rating value={4} max={10} />max defaults to 5. Stars are rendered as SVG icons; unfilled stars use the current
background colour.
Toast
import { ToastProvider, useToast } from '@boostdev/components';
// Wrap your app (or layout) once:
<ToastProvider>
<App />
</ToastProvider>
// Inside any component:
function SaveButton() {
const { showToast } = useToast();
return (
<button onClick={() => showToast('Saved!', 'success')}>
Save
</button>
);
}showToast(message, variant) — variant: 'success' · 'error' · 'info'
Toasts auto-dismiss after 5 seconds.
Form components
All form components accept standard HTML input attributes via spread in addition to their own props.
FormInput
import { FormInput } from '@boostdev/components';
<FormInput
label="Email"
name="email"
type="email"
hint="We'll never share your email"
error={errors.email}
/>Checkbox
import { Checkbox, CheckboxGroup } from '@boostdev/components';
<Checkbox
label="I agree to the terms"
name="agree"
error={errors.agree}
/>Group multiple checkboxes with a shared label using CheckboxGroup:
<CheckboxGroup legend="Notifications" required error={errors.notifications}>
<Checkbox label="Email" name="notify_email" />
<Checkbox label="SMS" name="notify_sms" />
<Checkbox label="Push" name="notify_push" />
</CheckboxGroup>Radio
import { Radio, RadioGroup } from '@boostdev/components';
<RadioGroup legend="Preferred contact" required>
<Radio label="Option A" name="choice" value="a" description="Optional supporting text." />
<Radio label="Option B" name="choice" value="b" />
</RadioGroup>Switch
import { Switch } from '@boostdev/components';
<Switch label="Enable notifications" name="notifications" />
<Switch label="Dark mode" name="dark-mode" defaultChecked />
<Switch label="On" name="theme" prefix="Off" size="small" />Sizes: small · medium (default) · large
SegmentedControl
import { SegmentedControl } from '@boostdev/components';
<SegmentedControl
name="view"
defaultValue="week"
aria-label="Calendar view"
options={[
{ value: 'day', label: 'Day' },
{ value: 'week', label: 'Week' },
{ value: 'month', label: 'Month' },
]}
onChange={value => console.log(value)}
/>Single-select control with a sliding thumb. All options have equal width (sized to the widest). Sizes: small · medium (default) · large.
Layout components
ButtonGroup
import { ButtonGroup, Button } from '@boostdev/components';
<ButtonGroup variant="flow" aria-label="Page navigation">
<Button>Back</Button>
<Button>Next</Button>
</ButtonGroup>| Prop | Type | Default |
|---|---|---|
| variant | 'flow' \| 'card' \| 'modal' \| 'content' | — |
| aria-label | string | — recommended when group purpose isn't clear from context |
Card
import { Card } from '@boostdev/components';
<Card>Basic card</Card>
<Card variant="elevated" padding="large">Elevated</Card>
<Card variant="outlined" textAlign="center">Outlined</Card>
<Card onClick={() => navigate('/detail')}>Clickable card</Card>| Prop | Type | Default |
|---|---|---|
| variant | 'default' \| 'elevated' \| 'outlined' | 'default' |
| padding | 'none' \| 'small' \| 'medium' \| 'large' | 'medium' |
| textAlign | 'start' \| 'center' \| 'end' | 'start' |
| onClick | () => void | — renders a <button> |
SectionHeader
import { SectionHeader } from '@boostdev/components';
<SectionHeader title="Our services" />
<SectionHeader
title="Welcome"
subtitle="Everything you need in one place"
size="large"
alignment="center"
/>| Prop | Type | Default |
|---|---|---|
| title | string | required |
| subtitle | string | — |
| size | 'small' \| 'medium' \| 'large' | 'medium' |
| alignment | 'start' \| 'center' \| 'end' | 'start' |
IconWrapper
import { IconWrapper } from '@boostdev/components';
<IconWrapper>
<svg>...</svg>
</IconWrapper>
{/* Mark as decorative when accompanied by a visible label */}
<IconWrapper aria-hidden>
<svg>...</svg>
</IconWrapper>Renders a circular container sized to its own font-size. Override the background colour via the
--icon-wrapper-color CSS custom property. Pass aria-hidden when the icon is decorative.
Utilities
cn
Composes class name strings, filtering out falsy values:
import { cn } from '@boostdev/components';
cn('card', isActive && 'card--active', [extraClass])
// → "card card--active extraClass"Overriding component styles
Every component exposes CSS custom properties as a styling API. Set them on a parent or via
style to adjust the component without writing extra CSS:
/* Override the button colour for a specific context */
.hero .button {
--button_color: var(--bds-brand);
--button_on-color: var(--bds-on-brand);
}All component styles sit in @layer component. You can also write rules in a higher layer:
@layer my-overrides {
.my-button {
border-radius: 0;
}
}Overriding active/checked state colour
Components with a checked or filled state (Checkbox, Radio, Slider, Progress, ProgressCircle)
expose a component-scoped variable that falls back to the global --bds-active token:
/* Override the active colour for a single checkbox */
.my-checkbox {
--checkbox_color-active: var(--bds-brand);
--checkbox_color-on-active: var(--bds-on-brand);
}
/* Override for all sliders in a section */
.settings-panel {
--slider_color-active: var(--bds-important);
}The two-level chain means you can also override at the global token level — see Theming below.
Theming
Components inherit from @boostdev/design-system-foundation tokens. Override semantic tokens to
retheme all components at once:
/* tokens.config.css — import after the design system CSS */
@layer tokens.override {
:root {
--bds-cta: var(--bds-BASE__color--orange);
--bds-on-cta: var(--bds-BASE__color--white);
--bds-interactive: var(--bds-BASE__color--orange);
}
}Active state tokens
The --bds-active family controls the checked/filled/selected colour across all form controls
and progress indicators. Override it once to retheme checkboxes, radio buttons, sliders, progress
bars, and the switch thumb simultaneously:
@layer tokens.override {
:root {
--bds-active: var(--bds-BASE__color--blue);
--bds-on-active: var(--bds-BASE__color--white);
--bds-active--subtle: var(--bds-BASE__color--blue--100); /* Switch track */
--bds-on-active--subtle: var(--bds-BASE__color--blue--900);
--bds-active--strong: var(--bds-BASE__color--blue--700); /* Switch thumb */
--bds-on-active--strong: var(--bds-BASE__color--white);
}
}Dark mode is handled automatically via [data-theme="dark"] or prefers-color-scheme.
Development
pnpm build # compile to dist/
pnpm typecheck # TypeScript check (library code only)
pnpm lint # ESLint
pnpm lint:css # Stylelint
pnpm test # Vitest — 364 tests
pnpm mcp:start # Start the MCP server locally (http://localhost:3001/api/mcp)
pnpm storybook # Storybook dev server on port 6006Adding a new component
- Create
src/components/{category}/{Name}/ - Add
Name.tsx,Name.module.css,index.ts - Wrap all CSS in
@layer component { ... } - Use only design token custom properties — no hard-coded colours, spacing, or font sizes
- Re-export from
src/index.ts - Run
pnpm build && pnpm typecheck && pnpm lint && pnpm lint:css && pnpm test
Design system reference (MCP server)
A live MCP server exposing this design system's components and tokens is deployed at:
https://ds.boostdev.nl/api/mcpAI coding agents can connect to it to look up component props and design tokens without reading the source code directly. It exposes five tools:
| Tool | Description |
|---|---|
| ds_list_components | All components grouped by category |
| ds_get_component | Full props interface for a named component |
| ds_list_tokens | Design tokens, filterable by group |
| ds_search | Keyword search across components and tokens |
| ds_get_guide | Returns the AGENTS.md usage guide |
To run the MCP server locally:
pnpm mcp:start # listens on http://localhost:3001/api/mcpPublishing a new version
npm version patch # or minor / major
git push --follow-tagsGitLab CI will build and publish to npm automatically on version tags.
