Change the wall and watch the material, the ink and the accent all answer to the picture.
Foundations
Barua's entire look lives in one token layer: an Apple-derived palette
tinted around Barua blue, glass materials for depth, and
light-dark() pairs that keep every color correct in both
themes. Learn the tokens once — every component on the following pages
is assembled from them, and re-theming means changing a variable, not
a component.
Color System
Every color token is a light-dark() pair, so a single
name is always correct in either scheme. The accent family drives
buttons, links, focus and selection; re-tint the whole system with
data-accent="indigo|purple|pink|teal|green" on
<html>.
System colors
The Apple system palette, tuned per scheme. Use them for meaning —
the semantic aliases --b-color-success,
--b-color-warning, --b-color-danger and
--b-color-info map to green, orange, red and accent, and
each has a translucent *-soft wash for badges and tints.
Grays
Apple systemGray 1–6, darkest to lightest in light mode. Reach for grays only when a fill or separator token doesn't fit — most neutral UI should use the translucent fills so it layers correctly.
Backgrounds & surfaces
Depth is a stack: canvas below, surfaces on top, elevated layers for
overlays. Cards and fields sit on --b-surface; nest with
--b-surface-2 and -3; menus and dialogs use
--b-elevated over a --b-scrim.
Text hierarchy
Four label levels, Apple-style, built from opacity so they read correctly on any surface. Primary for content, secondary for supporting copy, tertiary for hints and placeholders, quaternary for watermarks only — never for information people must read.
Wallpaper
Glass is a material, and a material only reads as one when there is something behind it. Over a flat page every level of glass collapses into the same grey — the blur has nothing to blur, the specular edge has nothing to catch. Give the wall a picture and the whole system comes alive at once.
So changing the background is not a decoration setting in Barua, it
is part of the system: put .b-wall on the surface that
carries it — usually <body> — and let the person
using the interface choose the picture. It costs nothing until one is
set, and the system learns its accent and its light/dark scheme from
whatever they choose. Some people will leave the wall bare; the ones
who don’t should be rewarded for it.
In a product you put .b-wall on <body>
and call Barua.wallpaper.set(src) or
Barua.wallpaper.setFile(file) — the picture is
remembered, resized in the browser, and the system learns from it.
The documentation site itself stays bare on purpose: reading
surfaces want quiet, application surfaces want a wall.
Adaptive accent & scheme
The system can learn from a picture — a user’s wallpaper.
Barua.adapt(imageOrUrl) samples the image and reads two
things from it: its most vibrant hue, which re-tints the accent
tokens on <html>, and its overall brightness,
which sets data-wall-scheme to
light or dark. Anything marked
.b-wallpaper-scope then resolves every
light-dark() token that way — so a dark wallpaper gets
light text and dark glass without touching the app’s theme.
Barua.adapt.reset() returns to stock. Try it — the whole
page follows:
A wall of its own: the surface below is a
.b-wallpaper-scope. Swap its wallpaper and the glass,
the ink and the accent inside it all follow the picture — the page
around it never changes theme.
Glass, ink and accent all read the picture behind them.
Typography
A text style is a whole set of metrics, not a size: every role below
states its own size, weight, leading and tracking together. Small text
is not body text made smaller — a caption opens its tracking slightly
and tightens its leading, which is why inheriting those values from
the page would be wrong. Headings of the same size as a role use the
same metrics as that role, so <h5> and
.b-headline are typographically identical.
SF first. --b-font-sans resolves to SF Pro on Apple
hardware and degrades gracefully everywhere else, so the system feels
native without shipping a webfont. Weight, tracking and leading are
tokens too — pair tight tracking with large sizes, wide tracking with
uppercase labels, and leave body text alone.
| Token | Value | Use it for |
|---|---|---|
--b-font-sans | SF Pro Text / Display, system fallbacks | All interface text |
--b-font-mono | SF Mono, Menlo, Consolas | Code, tokens, technical values |
--b-font-rounded | SF Pro Rounded, system fallbacks | Friendly numerals, badges, empty states |
--b-weight-regular | 400 | Body copy |
--b-weight-medium | 500 | Labels, chips, quiet emphasis |
--b-weight-semibold | 600 | Headlines, buttons, table headers |
--b-weight-bold | 700 | Titles, stat numerals |
--b-tracking-tight | -0.022em | Large titles |
--b-tracking-text | -0.011em | Body text (default) |
--b-tracking-wide | 0.06em | Overlines, uppercase labels |
--b-leading-tight | 1.2 | Titles and headings |
--b-leading-snug | 1.35 | Dense UI, code blocks |
--b-leading-body | 1.5 | Reading text (default) |
--b-leading-loose | 1.65 | Long-form articles |
Font Scale
The Apple HIG scale, exposed as one class per role. Elements
h1–h6 map onto it automatically; use the
classes when semantics and size need to differ — a card title that is
an h3 but should read as a headline, for instance.
.b-large-title
.b-title1
.b-title2
.b-title3
.b-headline
.b-body
.b-callout
.b-subheadline
.b-footnote
.b-caption
.b-overline
.b-truncate
.b-line-clamp-2 and .b-line-clamp-3 for a fixed number of lines.b-text-uppercase
.b-text-lowercase; the overline bundles case with colour and tracking, these do not.b-tabular-nums
Spacing
A 4px base scale. Use 1–3 for padding inside controls, 4–6 between related elements, 8–12 between groups, and 16–24 between page sections. If a gap isn't on the scale, it isn't in the system.
--b-space-10.25rem · 4px--b-space-20.5rem · 8px--b-space-30.75rem · 12px--b-space-41rem · 16px--b-space-51.25rem · 20px--b-space-61.5rem · 24px--b-space-82rem · 32px--b-space-102.5rem · 40px--b-space-123rem · 48px--b-space-164rem · 64px--b-space-205rem · 80px--b-space-246rem · 96pxGrid
.b-grid is 12 columns by default; set
--b-cols or use .b-grid--2/3/4/6 for equal
tracks, and span with .b-col-span-*.
.b-grid--auto auto-fills against
--b-col-min, and .b-card-grid /
.b-masonry handle card walls without media queries. Gaps
come from the spacing scale via .b-gap-*.
Layout
SwiftUI-style primitives: .b-vstack and
.b-hstack flow content with a token gap, and
.b-spacer absorbs the leftover space — that trio builds
most toolbars and headers. .b-container centers a page
column, .b-section spaces bands of content, and
.b-split + .b-panel make resizable
multi-pane layouts.
| Token | Value | Use it for |
|---|---|---|
--b-container-sm | 40rem · 640px | Reading columns, auth forms |
--b-container-md | 48rem · 768px | Articles, settings pages |
--b-container-lg | 64rem · 1024px | Default .b-container width |
--b-container-xl | 80rem · 1280px | Dashboards, data-dense views |
--b-sidebar-w | 16.25rem · 260px | Navigation sidebar |
--b-rail-w | 4.5rem · 72px | Collapsed icon rail |
--b-topbar-h | 3.25rem · 52px | Top bar height, scroll offsets |
Breakpoints
Custom properties can't drive @media, so breakpoints are
a convention: six shared stops, written by value, always mobile-first
with @media (min-width: …). Base styles serve the phone;
each stop only adds.
| Name | Min-width | Use it for |
|---|---|---|
xs | 480px | Large phones — bump paddings, keep one column |
sm | 640px | Landscape phones, small tablets — first two-column grids |
md | 768px | Tablets — .b-hide-mobile / .b-hide-desktop flip here |
lg | 1024px | Laptops — persistent sidebars, split views |
xl | 1280px | Desktops — right rails, TOCs, inspectors |
2xl | 1536px | Wide desktops — widen containers, cap line length |
Border Radius
Corners grow with the surface, Apple-style: xs for
inline code and checkboxes, sm for chips and kbd,
md for buttons, inputs and menus, lg for
panels and toolbars, xl for cards and modals,
2xl for sheets, and full for pills and
avatars. Never give a small control a bigger radius than the surface
it sits on.
--b-radius-xs
4px
--b-radius-sm
6px
--b-radius-md
10px
--b-radius-lg
14px
--b-radius-xl
20px
--b-radius-2xl
28px
--b-radius-full
pill
Shadows
Soft, layered, never harsh — each token stacks a contact shadow with
a wide ambient one, and --b-shadow-color deepens
automatically in dark mode. --b-shadow-accent is a blue
glow reserved for the FAB and other floating accent controls. Prefer
the elevation tokens below, which pair these with a hairline ring.
--b-shadow-xs
--b-shadow-sm
--b-shadow-md
--b-shadow-lg
--b-shadow-xl
--b-shadow-accent
Elevation
The elevation ladder pairs each shadow with a 1px hairline ring
(--b-ring) so edges stay crisp on any background. Use
the utilities: 1 for resting cards, 2 for raised cards and glass
toolbars, 3 for menus and popovers, 4 for modals and drawers, 5 for
the command palette. Level 0 removes elevation for flush surfaces.
.b-elevation-1
.b-elevation-2
.b-elevation-3
.b-elevation-4
.b-elevation-5
Opacity
Three stops, three meanings. hover (0.85) dims media and
imagery under the pointer, muted (0.6) de-emphasizes
without disabling, and disabled (0.4) is applied
automatically by :disabled and .is-disabled
— never hand-roll a disabled look.
base
1
--b-opacity-hover
0.85
--b-opacity-muted
0.6
--b-opacity-disabled
0.4
Blur
The blur scale feeds the glass materials. Rather than using raw blur
values, reach for the composite filters:
--b-glass-light (blur-sm + saturate 1.6),
--b-glass (blur-lg + saturate 1.8) and
--b-glass-heavy (blur-xl + saturate 2) — the saturation
boost is what makes color glow through the pane.
--b-blur-xs
4px
--b-blur-sm
8px
--b-blur-md
16px
--b-blur-lg
24px
--b-blur-xl
40px
Materials
Five glass thicknesses, from a whisper to nearly opaque. Thinner
materials show more of what's behind but hold less legible content —
use ultrathin and thin for hover surfaces,
regular for floating toolbars and glass cards,
thick for sheets, and chrome for app bars.
.b-material-accent adds a Barua-blue tint. Each class
pairs its --b-material-*-bg token with the
--b-glass backdrop filter and a hairline border.
.b-material-ultrathin
.b-material-thin
.b-material-regular
.b-material-thick
.b-material-chrome
backdrop-filter, and a blur
costs the GPU a pass over everything behind it — the price scales
with blur radius and painted area, not with how much markup you
wrote. Under
prefers-reduced-transparency the material tokens
resolve to opaque surfaces and the blurs to none, so
every glass surface in the system turns solid at once. The same
happens where backdrop-filter is unsupported, because
an ultrathin material without its blur is a 22% wash that text
cannot survive. Liquid refraction, the most expensive thing here,
does not start at all.
Baked glass
A blur is recomputed by the GPU on every frame that changes beneath it. When the thing beneath is the wallpaper, that blur has exactly one possible answer — so it can be worked out once, at the moment the picture is hung, instead of sixty times a second forever.
Barua.wallpaper bakes a 64px-wide copy when you set a
wallpaper. Drawn back at cover size that thumbnail is a
blur — that is what scaling up does to it — and it weighs about a
kilobyte. Both the wall and the copy are fixed, so the
copy lands exactly over the sharp original underneath and the panel
appears to look through itself. Switch a document over with one
attribute:
backdrop-filter blurs the photograph on every frame that changes.
The same photograph at 64×42, 1,956 bytes, drawn back at cover size.
Left is the real filter, right is a two-kilobyte thumbnail of the same photograph — the original is 617 KB. Scroll the page: both stay pinned to the viewport, which is exactly why the baked copy keeps sitting over the sharp original in a real app. Photograph by Starkvisuals on Pixabay.
<html data-b-glass="baked"> <!-- no backdrop-filter anywhere -->
Barua.glass.baked(true); <!-- or at runtime, remembered -->
Barua.glass.baked(false);
There is no backdrop-filter left in baked mode, so no
compositor layer per panel and no per-frame pass — what remains is
drawing an image. It is the right trade for wallpaper-backed chrome:
docks, sidebars, top bars, anything sitting still over the wall.
.b-has-wallpaper: with no picture there is nothing to
bake and the system stays as it was.
Backplate
SwiftUI's glassBackgroundEffect: glass put on any one
element rather than a component that is glass by nature.
.b-backplate is the regular material, a lit top edge, a
soft shadow and the system radius — for a floating control, a
caption over a picture, anything that needs to sit on something.
Now playing
Sauti Sol — Suzanna
Borders & Dividers
Four weights of edge, all translucent so they sit naturally on any
surface: --b-hairline for glass edges and elevation
rings, --b-separator for list rows and card borders,
--b-border for inputs and controls, and
--b-border-strong for their hover state.
.b-divider draws separator rules — vertical inside an
hstack, labeled for timeline breaks, and inset to align with list
content.
Forward
Archive
Motion
Motion is quick, physical and quiet: five durations, five curves, and
nothing animates that doesn't communicate. Small state changes use
instant and fast; surfaces that move use
slow with the spring curve. Everything collapses to near
zero under prefers-reduced-motion — see Accessibility.
Transitions
Components already transition on their tokens — hover these to feel
fast · standard on the button tint and
normal · out on the card lift.
Animation
Looping animation is reserved for indeterminate progress; keep it small and let it stop the moment the work resolves.
| Token | Value | Use it for |
|---|---|---|
--b-duration-instant | 100ms | Hover tints, pressed states |
--b-duration-fast | 150ms | Control transitions, focus rings, tooltips |
--b-duration-normal | 250ms | Reveals, card lifts, accordions |
--b-duration-slow | 350ms | Drawers, sheets, large surfaces |
--b-duration-slower | 500ms | Page-level moves, staged sequences |
--b-ease-standard | cubic-bezier(0.4, 0, 0.2, 1) | Default for property changes |
--b-ease-out | cubic-bezier(0.22, 1, 0.36, 1) | Entrances — fast start, gentle settle |
--b-ease-in | cubic-bezier(0.55, 0, 1, 0.45) | Exits and dismissals |
--b-ease-spring | cubic-bezier(0.32, 0.72, 0, 1) | Sheets and drawers — Apple slide feel |
--b-ease-bounce | cubic-bezier(0.34, 1.56, 0.64, 1) | Playful overshoot — toggles, badges |
Iconography
Icons are inline SVG — no font, no sprite request. Draw on a
20×20 viewBox with a 1.5 stroke,
stroke="currentColor" and round caps and joins, so every
icon inherits text color and matches the SF Symbols weight.
.b-icon sizes the box (--sm 16,
default 20, --lg 24, --xl 32) and
.b-icon-tile seats an icon on an accent-soft tile for
list leaders and feature rows. Decorative icons take
aria-hidden="true".
Accessibility
Accessibility is baked into the tokens, not bolted on. The focus ring
(--b-focus-ring, a 3.5px accent halo) appears on
:focus-visible only — keyboards get it, mouse clicks
don't. Tab through this demo to see it.
.b-sr-only keeps text available to screen readers while
visually hidden — use it to name icon-only controls when
aria-label won't do. .b-skip-link is the
first focusable element on every page (press Tab from the top of this
one), and .b-touch-target extends small controls to the
44px minimum without changing their look.
prefers-reduced-motion rule collapses every animation
and transition to 0.01ms — spinners, sheets and card lifts all
respect it with no per-component work. Design so that motion is
reinforcement, never the only signal.Dark Mode & Light Mode
The root declares color-scheme: light dark and every
color token is a light-dark() pair, so the whole system
follows the OS by default. Set data-theme="light|dark"
on <html> to force a scheme — the topbar's
data-b-theme-toggle button does exactly that and
persists the choice, which the inline <head>
script restores before first paint to avoid a flash.
Because tokens resolve against the nearest color-scheme,
you can force a subtree: the second card below is identical markup
inside a style="color-scheme: dark" wrapper.
#0a7aff light,
#0a84ff dark) to hold contrast on dark surfaces.Responsive Rules
Mobile first, always: base styles serve the smallest screen, and each
of the six breakpoints only adds. Most layouts shouldn't need media
queries at all — .b-grid--auto,
.b-card-grid and the stack primitives reflow on their
own. When you do write one, use min-width at a shared
stop.
/* Base styles serve the phone */
.inspector { display: none; }
/* lg · 1024px — laptops gain the inspector */
@media (min-width: 1024px) {
.inspector { display: block; }
}
On small screens: controls grow to the 44px touch target, hover-only
affordances get a visible fallback, .b-hide-mobile and
.b-hide-desktop swap chrome at 768px, and
.b-safe-area / .b-safe-bottom pad content
away from notches and home indicators.
@media (min-width: …) enhancements at the six shared
stops. Let auto-fill grids and stacks do the reflowing.
max-width overrides, invent
bespoke breakpoint values, or hide core content on mobile — reflow
it instead.
View Transitions
Two things, on the platform's View Transitions API. Opt in with
data-vt on <html> and same-origin
navigations cross-fade on the system's motion tokens (180ms out,
220ms in). Off by default, disabled automatically under reduced
motion, and inert in browsers without the API.
<html data-vt> <!-- pages now cross-fade on navigation -->
Shared elements
The second thing is the one that matters: a thumbnail in a list and
the hero it opens into are the same object to the person looking at
them, so they should be the same object to the browser. Give both
data-vt-name="cover-42" and it morphs one into the
other — across a navigation, or around a
Barua.transition() call within one page. Everything
unnamed still cross-fades. A name must be unique on the page; a
duplicate is warned about rather than silently mis-morphed.
Open one, then close it. In a browser without the API the panel simply appears, and under reduced motion it never animates.