Guide for adding or modifying components in packages/ that are distributed as npm packages. Use when creating new components, updating existing ones, adding styles/tokens, or writing Storybook stories for the react component library in packages/react.
Every component needs up to 3 files in packages/react/src/components/:
| File | Purpose |
|---|---|
ComponentName.tsx | React component with TypeScript types and JSDoc props |
ComponentName.css | Styles using CSS custom properties (design tokens) |
ComponentName.stories.tsx | Storybook stories for documentation and visual testing |
ComponentName.tsx)export interface XxxProps.d.tsdds- prefix convention (e.g. dds-callout, dds-callout-title)className prop for consumer extensibilityComponentName.css)tokens.css for all themeable values (colors, spacing, radii, etc.)dds- prefix: dds-componentname, dds-componentname-variantsrc/styles/tokens.css)Add tokens in three places within tokens.css:
:root -- Light mode (default) values.dds-dark, [data-dds-theme="dark"] -- Explicit dark mode@media (prefers-color-scheme: dark) -- Auto dark mode (OS preference)The explicit dark mode block and the @media block must have identical values.
This library is consumed by apps with various dark backgrounds. Dark mode tokens must work well on a range of dark surfaces, not just one specific shade.
#e5e7eb for text, #4b5563 for borders) rather than #000000 or #ffffff.transparent backgrounds unless the component explicitly supports a background color prop.rgba(59, 130, 246, 0.08)) so the component remains visible on various dark surfaces.#93c5fd) instead of the full-saturation colors used in light mode.Follow the existing pattern: --dds-{component}-{property} or --dds-{component}-{variant}-{property}.
Examples:
--dds-callout-padding: 1.25rem;
--dds-callout-caution-border: #dc2626;
--dds-callout-caution-bg: #fef2f2;
src/styles/components.css)Add an @import for the new component CSS file:
@import "../components/ComponentName.css";
This file is the entry point for PostCSS, which bundles all component styles into dist/styles.css.
src/index.ts)Add a named export:
export * from './components/ComponentName';
This allows consumers to import directly: import { ComponentName } from '@roadlittledawn/docs-design-system-react'.
ComponentName.stories.tsx)@storybook/react (Meta, StoryObj)tags: ['autodocs'] for automatic documentation generationparameters.docs.description.component field for component-level docs including "When to Use", "When Not to Use", and "Accessibility" sectionsExample structure:
import type { Meta, StoryObj } from '@storybook/react';
import { ComponentName } from './ComponentName';
const meta: Meta<typeof ComponentName> = {
title: 'Components/ComponentName',
component: ComponentName,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component: `Description here.
## When to Use
- ...
## When Not to Use
- ...
## Accessibility
- ...
`,
},
},
},
};
export default meta;
type Story = StoryObj<typeof ComponentName>;
export const Default: Story = {
args: { /* ... */ },
parameters: {
docs: {
source: {
code: `<ComponentName prop="value">children</ComponentName>`,
},
},
},
};
packages/react/package.json)When adding or modifying any component, bump the version in packages/react/package.json following semver:
0.x.Y → 0.x.Y+1) — bug fixes, doc-only changes, non-breaking tweaks0.X.0 → 0.X+1.0) — new props, new components, new features (backward-compatible)X.0.0 → X+1.0.0) — breaking changes (removed props, renamed components)"version": "0.12.1"
Run from the repo root:
# Build the react package (compiles TS + bundles CSS)
npm run build --workspace=packages/react
# Type-check
npm run type-check --workspace=packages/react
# Visually verify in Storybook
npm run storybook
The Storybook workspace has a prestorybook hook that builds the react package automatically, but it's good practice to build explicitly first to catch errors early.
Storybook auto-discovers stories from packages/react/src/**/*.stories.tsx -- no configuration needed.
When adding a new component or modifying an existing component's props:
packages/react/USAGE.md — props table and usage examplesstorybook/public/llms.txt — keep both files in syncA pre-commit hook blocks commits that change component files without also updating these docs. Both files follow the same format: import, props table, and basic example.
If you forget, the hook will show:
ERROR: Component files changed without updating AI docs.
Changed: packages/react/src/components/YourComponent.tsx
Please update packages/react/USAGE.md and storybook/public/llms.txt.
| File | Role |
|---|---|
packages/react/src/components/ | Component source files |
packages/react/src/styles/tokens.css | Design tokens (light + dark mode) |
packages/react/src/styles/components.css | CSS entry point (imports all component CSS) |
packages/react/src/index.ts | Package exports barrel file |
packages/react/package.json | Package config, build scripts, dependencies |
packages/react/tsconfig.json | TypeScript config (outputs to dist/) |
packages/react/postcss.config.js | PostCSS config (uses postcss-import) |
packages/react/USAGE.md | AI-context component API reference (travels with npm package) |
storybook/public/llms.txt | llmstxt.org file served at storybook-url/llms.txt with full component reference |
storybook/.storybook/main.ts | Storybook config (story discovery, aliases) |
storybook/.storybook/preview.ts | Storybook preview (imports dist/styles.css) |
components.css -- the component will render but have no styles in the bundled output.index.ts -- consumers won't be able to import the component.@media block -- users who rely on OS-level dark mode won't get dark styles.package.json -- if the component uses a new npm dependency, add it to packages/react/package.json (runtime deps in dependencies, types in devDependencies).parameters.docs.source.code on stories -- in production builds, React minifies component.name to a single letter, so the auto-generated source panel shows <c .../> instead of <ComponentName .../>. Every story must have an explicit parameters.docs.source.code string. This applies to all story types: args-only, args+render, and render-only. See Tabs.stories.tsx as the reference implementation.render functions -- Storybook defaults to dark mode (initialGlobals: { theme: "dark" }). Any wrapper element in a render function that contains readable text must use color: "var(--dds-tabs-panel-text)" (or another theme-aware token) rather than relying on the browser default (which is black and unreadable on dark backgrounds). Always set an explicit color on <p>, <div>, and other text-containing wrappers in story renders:
render: (args) => (
<p style={{ color: "var(--dds-tabs-panel-text)", fontFamily: "sans-serif" }}>
Inline text with <ComponentName {...args}>trigger</ComponentName> here.
</p>
),