Skip to content
Barua UI v0.2
System

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:

Connect
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:

Local
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.

tokens.css → actions.css
/* 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

FamilyPrefixExampleDefined in
Color — accent & system palette--b-color-*--b-color-accent, --b-color-danger-softcss/tokens.css
Color — primitives--b-blue-*, --b-gray-*--b-blue-500, --b-gray-5css/tokens.css
Backgrounds & surfaces--b-bg*, --b-surface*--b-surface-2, --b-elevated, --b-scrimcss/tokens.css
Text color hierarchy--b-text*--b-text-secondary, --b-text-quaternarycss/tokens.css
Fills, borders & separators--b-fill*, --b-border*--b-fill-tertiary, --b-separator, --b-hairlinecss/tokens.css
Materials (glass)--b-material-*, --b-glass*--b-material-regular-bg, --b-glass-heavycss/tokens.css
Typography--b-font-*, --b-text-<role>, --b-weight-*--b-text-body, --b-weight-semibold, --b-tracking-widecss/tokens.css
Space (4px scale)--b-space-*--b-space-4 = 1remcss/tokens.css
Radius--b-radius-*--b-radius-md, --b-radius-fullcss/tokens.css
Shadow & elevation--b-shadow-*, --b-elevation-*--b-elevation-3, --b-ring, --b-shadow-accentcss/tokens.css
Blur & opacity--b-blur-*, --b-opacity-*--b-blur-lg, --b-opacity-disabledcss/tokens.css
Motion--b-duration-*, --b-ease-*--b-duration-normal, --b-ease-springcss/tokens.css
Z-index layers--b-z-*--b-z-modal = 500, --b-z-tooltip = 800css/tokens.css
Control metrics--b-control-h-*, --b-container-*--b-control-h-md, --b-touch-target, --b-focus-ringcss/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/
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):

index.html
<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.

KindPatternExamplesNotes
Block.b-<block>.b-card, .b-btn, .b-tableOne component, one block, prefixed to avoid collisions.
Element.b-<block>__<element>.b-card__title, .b-menu__itemA part that only makes sense inside its block.
Modifier.b-<block>--<mod>.b-btn--primary, .b-table--stripedAdditive; 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 hookdata-b-*data-b-dialog, data-b-theme-toggle, data-b-cmdkWiring for barua.js; carries no styles.
Token--b-*--b-space-4, --b-color-accentAll 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:

Success Warning Danger

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.

ContractMeaningConsumed 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 validationAnnounced by AT on .b-input; styling comes from .b-field.is-invalid / .b-input.is-invalid / :user-invalid
.is-activeCurrent or toggled-on (no aria equivalent).b-btn, .b-sidebar__item, .b-topnav__link, .b-steps li
.is-selectedSelection.b-list-item, .b-table tr, .b-tree rows, .b-chip
.is-loadingBusy — pair with aria-busy="true".b-btn
:disabled / .is-disabledNon-interactive at 40% opacityEvery 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.

ModifierHeight tokenPxFont sizeAvailable on
--xs--b-control-h-xs24--b-text-captionButtons
--sm--b-control-h-sm30--b-text-footnoteButtons, inputs
(default)--b-control-h-md36--b-text-subheadlineButtons, inputs, selects
--lg--b-control-h-lg44--b-text-calloutButtons, inputs
--xl--b-control-h-xl52--b-text-bodyButtons

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.

Icon wrapper usage
<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>
Accessibility. Decorative icons get 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:

<head> — runs 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.

MethodReturnsBehavior
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).

NameMin-widthTypical use
xs480pxLarge phones; two-up chips and stats
sm640pxForms go two-column, dialogs stop being full-screen
md768pxTablet; the mobile/desktop pivot for visibility utilities
lg1024pxPersistent sidebar layouts
xl1280pxRight rails, inspectors, three-pane shells
2xl1536pxWide 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:

Desktop only (≥768px) Mobile only (<768px) Always visible

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:

GuaranteeMechanism
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 targets44px minimum via --b-touch-target; --b-control-h-lg hits it exactly, and .b-touch-target extends smaller controls invisibly.
ContrastText 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 motionA 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 firstReal <button>, <a>, <dialog>, <details>, <table> before any role attribute — components lean on native behavior and stay usable without JS where the platform allows.
Accessibility. Per-component notes appear in blocks like this one throughout the docs. They cover what the component does not do for you — labels, live regions and focus order remain your responsibility in application code.

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.

TokenValueUse for
--b-duration-instant100msHover tints, pressed-scale feedback
--b-duration-fast150msFades, color and border transitions, tooltips
--b-duration-normal250msLayout shifts, disclosure chevrons, card hover lift
--b-duration-slow350msSheets, drawers, modals — pair with --b-ease-spring
--b-duration-slower500msLarge surface transitions, chart draws
--b-ease-standard0.4, 0, 0.2, 1Default workhorse curve
--b-ease-out0.22, 1, 0.36, 1Entrances — start fast, settle gently
--b-ease-in0.55, 0, 1, 0.45Exits and dismissals
--b-ease-spring0.32, 0.72, 0, 1Apple-style sheet slide
--b-ease-bounce0.34, 1.56, 0.64, 1Playful 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:

Sign in
Card + field + button — nothing else.

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.

Verb-first, specific, sentence case: “Create project” · “Save changes” · “Move to trash” · “Send invite” · “Try again”.
Vague, shouting, or jargon: “New” · “OK” · “SUBMIT” · “Yes” (on a destructive confirm) · “Error 0x80070057 occurred”.

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 only
    No raw hex, px shadows or magic z-indexes — every value traces to a semantic token.
  • All states
    Hover, pressed, focus-visible, disabled — plus loading and selected where they apply, via the is-*/aria-* contract.
  • Dark mode & accents
    Verified in light and dark via light-dark(), and under all five data-accent tints.
  • Accessibility notes
    Semantic element, aria contract, 44px touch reach, contrast targets — and what the consumer must still provide.
  • Docs section
    A 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.