This document defines the rules and conventions for building components in the By Default design system. All contributors must follow these patterns to keep the system consistent and predictable.
How this system is organized
Components are split across four layers so the design system can be lifted into other products without dragging the BrandOS docs site along with it:
foundation— tokens, layout primitives, utilities. Lives inassets/css/design-system.css. Ships with every product.core— reusable components (button, card, dropdown, …) plus brand identity docs (brand-*.md). Lives indesign-system.css. Ships with every product.docs-site— components that only power this BrandOS docs site (asset-card, book-cover, dont-card, sticky-bar, copy-button). Lives inassets/css/docs-site.css. Does not ship.app— BrandOS-specific tools, integrations, and project content (calculators, world clock, ad preview, project case studies). Does not ship.
Every cms/*.md doc declares its layer in frontmatter:
---
title: "Button"
section: "Design System"
layer: "core"
---
The layer field is required — the doc generator validates it on every build. See CLAUDE.md §17 (Layer Discipline) for the seven rules that govern this split.
Naming convention
- Base class: (e.g. , , )
- Modifiers: (e.g. , )
- State classes: (e.g. , , , , ) — shared across components
- Utility overrides: use
!importantonly on utility classes (e.g. ) - JS hooks: use
data-*attributes, never CSS class names
Legacy note: The button component uses , , , , as modifiers. These predate the convention and are kept for backward compatibility. New components must use syntax.
Token rule
Every visual value in component CSS must reference a CSS custom property defined in :root. Never hardcode hex values, pixel values (except structural ones like border-radius: 50%), or raw font values.
Component tokens follow this pattern:
--component-property: var(--semantic-token);
Example:
--card-background: var(--background-primary);
--card-border: var(--border-faded);
--card-radius: var(--radius-m);
--card-padding: var(--space-xl);
File rule
| What | Where |
|---|---|
| Component CSS | assets/css/design-system.css under a numbered section heading |
| Component JS (if needed) | assets/js/component-name.js |
| Documentation source | cms/component-name.md |
| Generated docs page | design-system/component-name.html |
Section headings in design-system.css follow the format:
/* ------ 16. BADGE ------ */
Accessibility rule
Every interactive component must include:
| Requirement | Details |
|---|---|
| ARIA roles | Correct role attribute (e.g. role="tablist", role="tab", role="tabpanel") |
| ARIA attributes | aria-selected, aria-controls, aria-labelledby, aria-current, aria-label as needed |
| Keyboard support | Tab to focus, Enter/Space to activate, Escape to dismiss (where applicable), Arrow keys for navigation (tabs, menus) |
| Focus indicator | box-shadow: 0 0 0 2px color-mix(in srgb, var(--input-focus), transparent 75%) |
| Screen reader text | Use aria-label or visually hidden text for icon-only actions |
Component status
| Component | CSS class | Needs JS | Docs page |
|---|---|---|---|
| Button | No | button.md |
|
| Form elements | , , , | No | form.md |
| Callout | No | callout.md |
|
| Disclosure | details/summary |
No | disclosure.md |
| Badge | No | badge.md |
|
| Card | No | card.md |
|
| Breadcrumb | No | breadcrumb.md |
|
| Tabs | , | Yes (tabs.js) |
tabs.md |
| Progress | No | progress.md |
|
| Tooltip | [data-tooltip] |
No | tooltip.md |
| Toast | Yes (toast.js) |
toast.md |
|
| Code / Pre / Kbd | code, pre, kbd |
No | code.md |
| Mark / Abbr / Figure | mark, abbr, figure |
No | mark.md |
Asset Card, Book Cover, Don't Card, Sticky Bar and Copy Button are documented separately as docs-site components — they only exist to power this BrandOS docs site and are not part of the portable design system. See Layer Discipline and CLAUDE.md §17.
Dark mode rule
Components must not contain dark-mode-specific CSS. They rely entirely on semantic token overrides in [data-theme="dark"] and @media (prefers-color-scheme: dark).
The only exception is when a component uses brand-palette tokens directly (avoid this). If unavoidable, add the override to both the [data-theme="dark"] block and the @media fallback block.
Current exceptions:
markelement — uses via token, requires dark mode override- Scrollbar — uses neutral scale tokens directly, requires dark mode override
How to add a new component
Define tokens in
:root(indesign-system.css, after existing component tokens):/* -- Component tokens -- */ --component-property: var(--semantic-token);Add dark mode overrides if the component uses non-semantic tokens — add to both
[data-theme="dark"]and@media (prefers-color-scheme: dark)blocks.Write CSS in
design-system.cssunder a new numbered section:/* ------ N. COMPONENT NAME ------ */Write JS (only if needed) in
assets/js/component-name.js. Follow the existing pattern: IIFE, named functions, version logged to console.Write documentation in
cms/component-name.mdfollowing the standard frontmatter and content structure.Update this spec file — add the component to the status table above.
Regenerate docs:
cd cms/generator && npm run docgen