Design-System Infrastructure
The conventions that make Barua predictable — tokens, naming, layers and theming. Every component on every other page follows the rules on this one, so once you know them you can read (and extend) the whole system.
MCP server
The system is also a service. Rather than loading a 500 KB index into context, an agent can ask it questions — and, more usefully, hand back the markup it just wrote to be judged before shipping it. Hosted, so a coding agent that has never seen this repository can still use the system correctly:
claude mcp add --transport http barua-ui https://mcp.barua.tz/mcp
Five tools. search_components finds things by name,
purpose or class. get_component returns one
component’s anatomy — every class, the knobs it reads from your
markup, the states it answers to, and the canonical markup taken from
its own live demo. get_rules returns the rules, the grid
spans that exist and all 44 knobs. get_react answers the same question for the React package — the import line and the JSX for a component's documented example — and lint_markup takes
HTML or JSX and reports every violation: invented classes, hardcoded
colour, inline properties that are not knobs, native
<select> and date inputs, emoji used as icons, and
scroll panes that cannot scroll.
The endpoint answers a plain GET with its own
description, so mcp.barua.tz is
readable in a browser. Requests are rate limited and nothing is
stored; markup you send for linting is written to a temporary file,
checked against the stylesheets, and discarded.
To run it beside your own copy of the system instead — which keeps your markup on your machine — point the same server at a local checkout:
claude mcp add barua-ui -- node /path/to/barua-ui/mcp/barua-ui-mcp.mjs
Design Tokens
Every visual decision is a custom property prefixed --b-,
defined once in css/tokens.css. Tokens come in three
tiers: primitives are raw values
(--b-blue-500 is Barua blue), semantic
tokens give values meaning and carry the light/dark pair
(--b-color-accent), and components
consume the semantic tier — never the primitives.
/* 1 · primitive — a raw value */
--b-blue-500: #0a7aff; /* base — Barua blue */
/* 2 · semantic — meaning + light/dark pair */
--b-color-accent: light-dark(#0a7aff, #0a84ff);
/* 3 · component — consumes the semantic token */
.b-btn--primary { background: var(--b-color-accent); }
Token families
| Family | Prefix | Example | Defined in |
|---|---|---|---|
| Color — accent & system palette | --b-color-* | --b-color-accent, --b-color-danger-soft | css/tokens.css |
| Color — primitives | --b-blue-*, --b-gray-* | --b-blue-500, --b-gray-5 | css/tokens.css |
| Backgrounds & surfaces | --b-bg*, --b-surface* | --b-surface-2, --b-elevated, --b-scrim | css/tokens.css |
| Text color hierarchy | --b-text* | --b-text-secondary, --b-text-quaternary | css/tokens.css |
| Fills, borders & separators | --b-fill*, --b-border* | --b-fill-tertiary, --b-separator, --b-hairline | css/tokens.css |
| Materials (glass) | --b-material-*, --b-glass* | --b-material-regular-bg, --b-glass-heavy | css/tokens.css |
| Typography | --b-font-*, --b-text-<role>, --b-weight-* | --b-text-body, --b-weight-semibold, --b-tracking-wide | css/tokens.css |
| Space (4px scale) | --b-space-* | --b-space-4 = 1rem | css/tokens.css |
| Radius | --b-radius-* | --b-radius-md, --b-radius-full | css/tokens.css |
| Shadow & elevation | --b-shadow-*, --b-elevation-* | --b-elevation-3, --b-ring, --b-shadow-accent | css/tokens.css |
| Blur & opacity | --b-blur-*, --b-opacity-* | --b-blur-lg, --b-opacity-disabled | css/tokens.css |
| Motion | --b-duration-*, --b-ease-* | --b-duration-normal, --b-ease-spring | css/tokens.css |
| Z-index layers | --b-z-* | --b-z-modal = 500, --b-z-tooltip = 800 | css/tokens.css |
| Control metrics | --b-control-h-*, --b-container-* | --b-control-h-md, --b-touch-target, --b-focus-ring | css/tokens.css |
Rule. Components consume semantic tokens
only. If you find yourself reaching for --b-blue-500 or
a raw hex value inside a component, you want
--b-color-accent (or a new semantic alias) instead —
that is what keeps dark mode and accent re-tinting free.
Architecture & Layers
Barua is one stylesheet with a declared cascade.
css/barua.css opens with
@layer tokens, base, utilities, components; — later
layers win, so components can rely on base and utilities without
specificity wars. Your own overrides live unlayered,
and unlayered CSS always beats layered rules: a plain
.b-btn { border-radius: 0 } in your app stylesheet wins
without !important.
css/
├─ barua.css ← single entry point: @layer order + @import list
├─ tokens.css ← every design token @layer tokens
├─ base.css ← reset, type, focus, states @layer base
├─ utilities.css ← layout primitives, text roles @layer utilities
└─ components/ ← one file per family @layer components
├─ actions.css nav.css forms.css
├─ content.css feedback.css overlays.css
├─ charts.css media.css specialized.css
└─ productivity.css mobile.css auth.css
Consumers import exactly one file (plus the optional no-dependency JS helpers):
<link rel="stylesheet" href="css/barua.css">
<script defer src="js/barua.js"></script>
Naming Conventions
BEM with a b- namespace. If a class starts with
.b- it ships with Barua; if it starts with
.is- it is runtime state; if an attribute starts with
data-b- it is a JS behavior hook, never a styling hook.
| Kind | Pattern | Examples | Notes |
|---|---|---|---|
| Block | .b-<block> | .b-card, .b-btn, .b-table | One component, one block, prefixed to avoid collisions. |
| Element | .b-<block>__<element> | .b-card__title, .b-menu__item | A part that only makes sense inside its block. |
| Modifier | .b-<block>--<mod> | .b-btn--primary, .b-table--striped | Additive; always keep the base class alongside. |
| State | .is-* / aria-* | .is-active, .is-selected, [aria-pressed="true"] | Toggled at runtime. Prefer the aria attribute when a semantic one exists. |
| Data hook | data-b-* | data-b-dialog, data-b-theme-toggle, data-b-cmdk | Wiring for barua.js; carries no styles. |
| Token | --b-* | --b-space-4, --b-color-accent | All custom properties share the same namespace. |
Component Variants
Variants are modifier classes along two standard axes. The visual axis sets prominence — primary, tinted, outline, ghost, glass, danger — and reads the same on every component that offers it. Buttons carry the full set:
The semantic axis sets tone — success, warning,
danger — and maps straight onto the semantic color tokens, so the
same modifier names appear on .b-badge,
.b-alert, .b-toast, .b-progress
and .b-status:
Rule. Variants change look, not behavior. A
.b-btn--danger is still a button — same height, focus
ring, loading spinner and keyboard handling as every other button.
Behavior differences deserve a different component, not a variant.
Component States
State is expressed twice: an aria-* attribute for
assistive technology and, where no semantic attribute exists, an
.is-* class for styling. Barua's selectors target both,
so setting the aria attribute alone is enough for the components
below. The full state language lives in
Interaction.
| Contract | Meaning | Consumed by |
|---|---|---|
aria-pressed="true" | Toggle is on | .b-toggle-btn, .b-chip |
aria-selected="true" | Selected within a set | .b-tab, .b-segmented__item, .b-combobox__option |
aria-current="page" | Current location | .b-breadcrumbs links, .b-pagination__item |
aria-expanded="true" | Disclosure / menu open | .b-menubar__item (native <details> uses [open]) |
aria-invalid="true" | Failed validation | Announced by AT on .b-input; styling comes from .b-field.is-invalid / .b-input.is-invalid / :user-invalid |
.is-active | Current or toggled-on (no aria equivalent) | .b-btn, .b-sidebar__item, .b-topnav__link, .b-steps li |
.is-selected | Selection | .b-list-item, .b-table tr, .b-tree rows, .b-chip |
.is-loading | Busy — pair with aria-busy="true" | .b-btn |
:disabled / .is-disabled | Non-interactive at 40% opacity | Every control, globally via base.css |
Component Sizes
One height scale for every control, held in the
--b-control-h-* tokens. Modifiers are
--xs, --sm, medium by default,
--lg, --xl; font size and radius step down
or up with the height.
| Modifier | Height token | Px | Font size | Available on |
|---|---|---|---|---|
--xs | --b-control-h-xs | 24 | --b-text-caption | Buttons |
--sm | --b-control-h-sm | 30 | --b-text-footnote | Buttons, inputs |
| (default) | --b-control-h-md | 36 | --b-text-subheadline | Buttons, inputs, selects |
--lg | --b-control-h-lg | 44 | --b-text-callout | Buttons, inputs |
--xl | --b-control-h-xl | 52 | --b-text-body | Buttons |
Touch targets. 44px (--b-touch-target)
is the minimum hit area on touch layouts — exactly
--b-control-h-lg, so prefer --lg controls on
mobile. Smaller controls can keep their compact look and still hit the
target with the .b-touch-target utility, which expands
the hit area invisibly.
Icon System
Icons are inline SVGs on a 20×20 viewBox: 1.5px strokes,
round caps and joins, fill="none", and
currentColor throughout so they inherit text color in
every variant and theme. The .b-icon wrapper sizes them —
--sm 16px, default 20px, --lg 24px,
--xl 32px. Inside buttons no wrapper is needed; the
button scales its own SVG.
<span class="b-icon b-icon--sm"> <!-- 16px; default 20, --lg 24, --xl 32 -->
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="m4 10.5 4 4 8-9" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
aria-hidden="true". An icon that is the only content of
a control never labels it — put the name in aria-label
on the control, as every icon button on
Actions does.Theme System
Every color token is declared once with light-dark() and
:root sets color-scheme: light dark, so with
no attribute at all the UI follows the system appearance. Setting
data-theme="light" or data-theme="dark" on
<html> forces a scheme by flipping
color-scheme — no second stylesheet, no class swap. Each
docs page restores the saved choice before first paint:
<script>
try {
const t = localStorage.getItem("barua-theme");
if (t) document.documentElement.dataset.theme = t;
} catch (e) {}
</script>
Accent re-tinting
data-accent="indigo|purple|pink|teal|green" remaps
--b-color-accent and its hover/soft/text companions.
Because components only ever consume the semantic accent tokens, one
attribute re-tints everything below it — set it on
<html> for the whole app, or on any subtree:
JS API
barua.js exposes Barua.theme and the
data-b-theme-toggle click hook (the moon/sun button in
the topbar uses it). Preference persists in
localStorage under barua-theme.
| Method | Returns | Behavior |
|---|---|---|
Barua.theme.get() | "light" | "dark" | "auto" | Current mode; "auto" when no override is set. |
Barua.theme.set(mode) | — | Applies and persists "light" or "dark"; "auto" removes the override and returns to the system scheme. |
Barua.theme.toggle() | — | Flips the resolved appearance — in auto mode it reads the system preference first, then sets the opposite. |
Responsive System
Mobile-first: base styles target small screens and
min-width queries layer on refinements at the breakpoint
values below (a documented convention in tokens.css —
media queries cannot read custom properties, so the values are used
literally).
| Name | Min-width | Typical use |
|---|---|---|
xs | 480px | Large phones; two-up chips and stats |
sm | 640px | Forms go two-column, dialogs stop being full-screen |
md | 768px | Tablet; the mobile/desktop pivot for visibility utilities |
lg | 1024px | Persistent sidebar layouts |
xl | 1280px | Right rails, inspectors, three-pane shells |
2xl | 1536px | Wide dashboards; container max-widths do the capping |
Two visibility utilities pivot at 768px: .b-hide-mobile
hides below it, .b-hide-desktop hides at or above it —
resize to watch these swap:
Container strategy. Content widths are capped by
.b-container (64rem via --b-container-lg)
with --sm 40rem, --md 48rem,
--xl 80rem and --fluid variants — pick the
narrowest container that fits the content instead of writing
page-level media queries. Shell metrics
(--b-sidebar-w, --b-rail-w,
--b-topbar-h) are tokens, so app chrome and content
agree on the same numbers.
Accessibility System
Accessibility is infrastructure, not a per-component afterthought. The baseline contract every component inherits:
| Guarantee | Mechanism |
|---|---|
| Visible focus | :focus-visible applies the --b-focus-ring token (3.5px accent halo) globally in base.css — keyboard users always see where they are, pointer users never see it flash. |
| Touch targets | 44px minimum via --b-touch-target; --b-control-h-lg hits it exactly, and .b-touch-target extends smaller controls invisibly. |
| Contrast | Text meets 4.5:1 against its surface, UI components and focus indicators meet 3:1 — in both schemes; the text-hierarchy tokens are tuned for this. |
| Reduced motion | A global prefers-reduced-motion override in base.css collapses every animation and transition to 0.01ms — no per-component opt-in required. |
| Screen reader plumbing | .b-sr-only for visually hidden text, .b-skip-link as the first focusable element of every page (press Tab from here to see it). |
| Semantic HTML first | Real <button>, <a>, <dialog>, <details>, <table> before any role attribute — components lean on native behavior and stay usable without JS where the platform allows. |
Motion System
Five durations, five curves — all tokens, so timing stays consistent across components. Small feedback is near-instant; the bigger the surface, the longer and springier the move.
| Token | Value | Use for |
|---|---|---|
--b-duration-instant | 100ms | Hover tints, pressed-scale feedback |
--b-duration-fast | 150ms | Fades, color and border transitions, tooltips |
--b-duration-normal | 250ms | Layout shifts, disclosure chevrons, card hover lift |
--b-duration-slow | 350ms | Sheets, drawers, modals — pair with --b-ease-spring |
--b-duration-slower | 500ms | Large surface transitions, chart draws |
--b-ease-standard | 0.4, 0, 0.2, 1 | Default workhorse curve |
--b-ease-out | 0.22, 1, 0.36, 1 | Entrances — start fast, settle gently |
--b-ease-in | 0.55, 0, 1, 0.45 | Exits and dismissals |
--b-ease-spring | 0.32, 0.72, 0, 1 | Apple-style sheet slide |
--b-ease-bounce | 0.34, 1.56, 0.64, 1 | Playful overshoot — badges, toasts |
Rule. Motion communicates hierarchy — it tells the
eye what changed and how big the change was, never decorates. And
because the reduced-motion override is global, every token-driven
animation already respects prefers-reduced-motion.
Interaction States
The full interaction-state language — hover, pressed
(:active / .is-pressed), drag
(.is-dragging, [draggable] cursors),
selection rectangles, drop indicators and focus choreography — is a
pattern page of its own. This page defines the naming; see
Interaction for live demos of each
state applied across components.
Composition Patterns
Big surfaces are assembled, not invented. An auth screen is
.b-card + .b-field + .b-btn
(packaged as .b-auth-card on
Authentication); a dashboard is
.b-grid + .b-card + .b-stat +
.b-chart (see Productivity
and Data Visualization). The same three
primitives compose a working sign-in card with zero new CSS:
Rule. Build pages from patterns, patterns from components, components from tokens. If a layer skips a level — a page styling raw hex, a pattern inventing a one-off control — consistency leaks out exactly there.
Content Guidelines
Voice is part of the system. Buttons are verb-first and specific —
they say what happens, not what the dialog is about. Sentence case
everywhere: buttons, labels, headings, menu items. Errors say what
went wrong and what to do next, in words a person would use; error
codes belong in logs, not in .b-error text.
Usage & Contribution Guidelines
Compose first. If a design can be assembled from
existing components plus layout utilities, compose it in markup — do
not add CSS. Add a new component only when a pattern recurs across
several screens or needs its own state contract. New components get a
block name, live in css/components/ inside
@layer components, and are imported from
barua.css. Before shipping one:
-
Tokens onlyNo raw hex, px shadows or magic z-indexes — every value traces to a semantic token.
-
All statesHover, pressed, focus-visible, disabled — plus loading and selected where they apply, via the is-*/aria-* contract.
-
Dark mode & accentsVerified in light and dark via light-dark(), and under all five data-accent tints.
-
Accessibility notesSemantic element, aria contract, 44px touch reach, contrast targets — and what the consumer must still provide.
-
Docs sectionA live demo with variants, sizes and states on the relevant page — undocumented components don't exist.
Documentation Conventions
These docs are themselves built from Barua — docs.css
styles only the chrome (shell, hero, demo frames), so every demo you
see is the real system rendering itself. Each section follows one
anatomy: .docs-section with an h2[id],
short prose, a live .docs-demo, and a
.docs-a11y note where the component has sharp edges.
docs.js generates the collapsible HTML block under every
demo from its actual markup — the code you copy is the code that
rendered, and can never drift. Demos marked data-no-code
skip the block when the markup would be noise. The same script builds
the right-rail table of contents from the section headings and feeds
the ⌘K palette from the sidebar, so navigation stays in
sync with the pages for free.