Skip to content

Repository files navigation

Vibrant Gio — design tokens

Vibrant Gio is a generative design system for Gio desktop applications. The entire colour system derives from one brand seed — currently #723ad4 — through theme/tokens.FromSeed: every colour role carries a nine-step functional ramp (100–900) on a shared lightness scale, with the role's base pinned separately so the brand colour is reproduced exactly, and dark mode is the paired ramp — the same step keeps the same job in both modes, so nothing is assigned twice. The contrast gate is APCA (step 900 reaches |Lc| 90 and step 700 |Lc| 60 over the 100/200 grounds, every pin's on-colour |Lc| 60 over its pin); WCAG 2 ratios are reported alongside but do not gate.

This directory is generated by theme/export (cmd/vg-tokens); do not edit it by hand — regenerate it.

Files

  • styles.css — the token sheet: one :root block (light colours and elevation levels plus every mode-invariant scale, comfortable density), one .dark class override block (the paired dark colours and levels) and one .compact class override block (the compact density metrics only). Add class="dark" to the root element to switch modes, class="compact" to any subtree to densify it; the two switches are orthogonal. Below the token blocks sits the component class layer (.btn and its modifiers) — see Component classes.
  • theme.json — the generative parameters; see the reproducibility contract below.
  • foundations/color.html — every ramp, pin, step purpose and measured APCA Lc / WCAG ratio per text pair, in both modes.
  • foundations/type.html — every type role at its real metrics.
  • fonts/ — the faces behind --font-family and --font-family-code, self-hosted with their licences: the same Roboto regular and medium the Gio applications embed, and Roboto Mono from the font repository.
  • foundations/layout.html — the spacing scale, the control metrics at both density settings, the radius scale and tonal elevation as rendered specimens.
  • components/*.html — hand-authored component pages (not generated): every class-layer component in every register and state, at both densities, as copyable markup. buttons.html covers .btn and .badge, forms.html the native-element form controls.

Each page reads only from styles.css — every styled value is a var(--…) reference — and carries a light/dark toggle. Annotation numbers are printed for both modes, labelled L and D.

Token families

Family Tokens Notes
--color-neutral-<step> --color-neutral-100--color-neutral-900 the neutral ramp; steps in hundreds
--color-primary-<step> --color-primary-100--color-primary-900 the primary ramp; steps in hundreds
--color-secondary-<step> --color-secondary-100--color-secondary-900 the secondary ramp; steps in hundreds
--color-tertiary-<step> --color-tertiary-100--color-tertiary-900 the tertiary ramp; steps in hundreds
--color-error-<step> --color-error-100--color-error-900 the error ramp; steps in hundreds
--color-success-<step> --color-success-100--color-success-900 the success ramp; steps in hundreds
--color-warning-<step> --color-warning-100--color-warning-900 the warning ramp; steps in hundreds
--color-info-<step> --color-info-100--color-info-900 the info ramp; steps in hundreds
pins & semantic layer --color-bg, --color-surface, --color-text, --color-divider, --color-inverse-surface, --color-on-inverse-surface, --color-highlight, --color-accent, --color-on-accent, --color-accent-hover, --color-accent-pressed, --color-secondary, --color-on-secondary, --color-tertiary, --color-on-tertiary, --color-error, --color-on-error, --color-success, --color-on-success, --color-warning, --color-on-warning, --color-info, --color-on-info, --color-error-container, --color-on-error-container, --color-success-container, --color-on-success-container, --color-warning-container, --color-on-warning-container, --color-info-container, --color-on-info-container, --color-error-on-inverse, --color-success-on-inverse, --color-warning-on-inverse, --color-info-on-inverse, --color-badge-neutral-fill, --color-badge-neutral, --color-badge-success-fill, --color-badge-success, --color-badge-warning-fill, --color-badge-warning, --color-badge-error-fill, --color-badge-error, --color-badge-info-fill, --color-badge-info, --color-btn-tonal-fill, --color-btn-tonal, --color-btn-tonal-fill-hover, --color-btn-tonal-hover, --color-btn-tonal-fill-active, --color-btn-tonal-active, --color-control-border, --color-card-border, --color-dialog-border, --color-popover-border, --color-focus-ring, --color-focus-ring-on-accent pinned bases, their on-colours, the ramp-resolved surface/divider, the inverse pair the counterpart scheme's ramp resolves, the reserved highlighter no status hue may serve, and the edges and rings each ramp measures for itself — a resting border per level, for the page, the outlined card, the dialog and the popover, and one focus ring for the whole scheme, measured against every level at once, plus the accent fill a filled button's ring lies on, which belongs to no level at all
--font-family --font-family the typeface every prose role uses
--font-family-code --font-family-code the monospace typeface the code role uses
--font-<role>-* roles display-large, display-medium, display-small, headline-large, headline-medium, headline-small, title-large, title-medium, title-small, label-large, label-medium, label-small, body-large, body-medium, body-small, code; each with -size, -line-height, -weight, -tracking px sizes, CSS numeric weights; code is the mono style outside the MD3 grid, at body-medium's metrics
--density-<metric> --density-control-height, --density-chip-height, --density-padding-x, --density-padding-y, --density-min-hit-target control metrics, px; :root is comfortable, .compact overrides all but the hit-target floor
--space-<key> --space-0, --space-1, --space-2, --space-3, --space-4, --space-5, --space-6, --space-8, --space-10, --space-12, --space-16, --space-20, --space-24 the 4-pt spacing grid, px
--radius-<key> --radius-none, --radius-sm, --radius-base, --radius-md, --radius-lg, --radius-xl, --radius-2xl, --radius-3xl, --radius-full corner radii, Tailwind naming, px
--elevation-<level> --elevation-backdrop, --elevation-chrome, --elevation-0, --elevation-1, --elevation-2, --elevation-3 tonal surface fills — the DEFAULT elevation cue; ordered from the backdrop up toward the reader, and resolved per scheme, so both blocks state their own six
--elevation-<level>-<state> --elevation-backdrop-hover, --elevation-backdrop-active, --elevation-chrome-hover, --elevation-chrome-active, --elevation-0-hover, --elevation-0-active, --elevation-1-hover, --elevation-1-active, --elevation-2-hover, --elevation-2-active, --elevation-3-hover, --elevation-3-active each level's own interaction walk — what a control with no fill of its own takes the surface under it to when hovered or pressed. Taken FROM the level's fill rather than named as a ramp step, because a level is not a ramp step in both schemes
--shadow-<level> --shadow-backdrop, --shadow-chrome, --shadow-0, --shadow-1, --shadow-2, --shadow-3 dp box-shadows — the OPT-IN cue for floating transients (menus, dialogs, tooltips) layered over the tonal fill; resting surfaces use the fill alone
--ease-<name> --ease-standard, --ease-standard-accelerate, --ease-standard-decelerate, --ease-emphasized, --ease-emphasized-accelerate, --ease-emphasized-decelerate MD3 easing presets as cubic-bezier(); emphasized is the documented single-bezier stand-in for MD3's two-segment path
--duration-<stop> --duration-x-fast, --duration-fast, --duration-normal, --duration-slow, --duration-x-slow MD3-pinned duration stops, ms; the reduce-motion variant zeroes them
interaction states --focus-ring-width, --state-disabled-opacity the ring's 2 px stroke and the disabled fade fraction for color-mix() — both mode-invariant, unlike the ring's colour, which is measured against a ground that flips and so sits with the colours above
scrim --color-scrim the modal backdrop dimmer: translucent black, identical in both modes — a scrim dims by reducing luminance, so it never flips with .dark. The alpha is the sRGB-compositing equivalent of the Gio pattern's 50% linear-space black

Step purposes

Step Job
100 tinted fill · app ground
200 tinted fill · one step off the app ground
300 hover · subtle border, separator
400 / 600 / 800 intermediate steps; interaction states walk through them
500 mid-value reference · strong border
700 low-contrast text (Lc ≥ 60) · pressed
900 high-contrast body text (Lc ≥ 90) · pressed

The pinned base — not a ramp step — is the solid fill; hover and pressed on a solid walk one and two steps from the pin toward 900.

Component classes

styles.css ends with the component class layer, defined over the tokens above — no literal colours anywhere, the only literal lengths being the component constants the Gio side also hardcodes (the 20 dp checkbox/radio glyph, its 10 dp dot, the 16 dp dropdown chevron, the 1/2 dp input borders) — so it re-brands, flips to .dark and densifies to .compact with the sheet. Every pointer/keyboard state rule carries a forcing twin class (.is-hover, .is-active, .is-focus, .is-checked) grouped into the same rule, so a static page can show a state with exactly the live declarations; disabled is forced with the native attribute.

.btn is the button, filled by default: the accent pin under its on-colour. Two modifier classes select the quieter emphasis variants — .btn.tonal (the accent's tint, --color-btn-tonal-fill under --color-btn-tonal: the same recipe .badge wears, one hue at two strengths) and .btn.ghost (no ground at rest; neutral 700 text). Interaction states resolve as the step walks above: hover walks one step (:hover), pressed and selected two (:active, .selected); a filled button's solid fill walks via the emitted --color-accent-hover / --color-accent-pressed stops, and a tonal button through its own -hover and -active pairs, whose foreground moves with the fill. Keyboard focus (:focus-visible) keeps the resting fill and draws the ring: --focus-ring-width of --color-focus-ring, the one ring the scheme carries — the step of the primary ramp nearest its mid-value step that reaches 3:1 against every level at once, so a control wears the same ring wherever it is put. It also parts from every resting border in luminance rather than in hue alone, so focus stays findable where a display or a system setting takes the colour away, and it is never the accent fill itself, which is what a checked control already paints. --color-focus-ring-on-accent is the sole exception, for the ring a filled button insets in its own fill: that fill is a step of the primary ramp too, and the scheme's ring cannot read on it. Same ring, same width, same 3:1 floor in every variant. Disabled (:disabled) fades each colour to --state-disabled-opacity of its alpha. A ghost has no selected treatment: it stays quiet. .btn.icon is the icon-only form: a square the density's control height on a side, the glyph (an inline SVG on currentColor) inset by the density's vertical padding.

.badge is the inline annotation: label-medium text over a tinted field of its own hue. One hue at two strengths — a pale fill for the field and the same hue at reading strength for the word — and never the inverted pairing, which is what .btn uses and what a badge must not claim to be. No boundary and no vertical padding, so its height is the role's line box; the side padding is --space-2 and the corner is --radius-base, deliberately not the pill .chip wears. The default is the plain category label; .badge.success / .badge.warning / .badge.error / .badge.info are the four statuses, differing in hue alone. Both halves are tokens because both are derived against a ground rather than named on a ramp — the fill against the page, the foreground against the fill. Compose them for status; never inline-style a status colour. A badge is read, not used: no interaction states.

The form controls dress native elements — no script anywhere: .input (text <input>, and <select class="input select"> inside a .select-wrap for the chevron), .checkbox and .radio on their native input types with appearance: none. They resolve exactly as components/input does: Surface ground under body-large text, --color-control-border on the resting edge of all four controls, neutral 700 placeholder and chevron, focus promoting the border to the ring (2 dp on the text field, the shared outline on checkbox/radio), disabled fading every colour via color-mix(). That border is the neutral rung the ramp measures as reaching 3:1 against the window ground, which is 600 in the light scheme and 500 in the dark; the named rung it replaced read below the floor in one of them, at 2.67:1 in the scheme most people read in. The edge follows the control into a raised host: a surface that fills a deeper level declares --ground-border beside its own fill, the rules name it with the paper's own token as the fallback, and every control inside re-derives — the same walk against the same fill the host measures its own outline against, which is why a checkbox in a dialog wears the dialog's edge. In the dark scheme the page's own rung reads 2.62:1 over a level-2 fill and 1.80:1 over a level-3 one, both under the floor; in the light scheme it clears every level and the handed-down token repeats. The ring does not follow, because it never left: it is measured against every level at once and is one colour for the scheme. Checked, the box is the accent fill under a check mark in the on-accent pin, drawn from the icon set's grid as two gradient bands — a fill says a colour was applied and only the mark says what it means. The radio's selected state is the accent ring and 10 dp dot around a Surface gap.

Elevation: default vs opt-in

Elevation is tonal, and it climbs toward the light: in both schemes, every level is lighter than the one beneath it. One perceptual rule, no second rule for the dark scheme and no mirror — a surface nearer the viewer catches more light, and reflectance does not invert when the room goes dark. Six levels, counted from the backdrop up toward the reader:

Level What wears it
--elevation-backdrop nothing: the bare window plane, showing wherever nothing stands; the window's darkest region
--elevation-chrome the window's furniture — navbar, toolbar, sidebar, inspector, status bar, pane
--elevation-0 the content surface, the bg pin
--elevation-1 raised on the content — cards, code fences, text fields
--elevation-2 floating — dialogs, toasts
--elevation-3 floating, nearest the scheme's light extreme — menus, popovers, tooltips

Read that down and the fill gets lighter, in :root and in .dark alike. Chrome is window-scale only: the trim inside a component or a pattern — a card's header, a dialog's footer — is that thing's structure and takes no level of its own. The chrome level is the one the ramp does not place: its step under the content is measured off the platform rather than derived, and the two schemes measure differently — about 4.9 L* under light content, about 1.5 under dark, where a full ramp step would read as a hole rather than as furniture. That asymmetry is the platform's own, not a rule mirrored between the schemes. The backdrop takes that step scaled by the ramp's own proportion, because no platform capture shows a window plane beneath its furniture to measure. A chrome region that floats is still chrome and still fills at the chrome level: what says it is a floating object is its own hairline edge and its shadow, never a lighter fill. The levels stop at 3: desktop has no six-deep stack. Note the sizes — a light scheme has spent almost all of the tonal axis on its content, so its levels above the content are separated by a fraction of an L* and the derived hairline (--card-border, --dialog-border, --popover-border) is what says where a surface is. That is what the desktop applications this system is judged against measure too.

The surface fill is the default cue. The dp shadow is the opt-in secondary cue, reserved for floating transients — menus, dialogs, tooltips — which layer --shadow-N over their tonal fill. Resting surfaces never cast one.

Density

Two published settings, one variable family: comfortable (36 dp controls, 32 dp chips, 16/8 dp padding) is the :root default; compact (28 dp controls, 24 dp chips, 12/6 dp padding) is the .compact class override, scoping to any subtree the way .dark scopes colours. The chip height is the control height less 4 dp in both settings — one relation, not a second scale. --density-min-hit-target (44 dp, WCAG 2.5.5) is deliberately not overridden: compact shrinks the drawn control, never the clickable area. theme.json records both settings' metrics plus which one the theme runs.

Motion

The MD3 easing presets are emitted as cubic-bezier() variables and the five duration stops in ms, each pinned to one MD3 duration role. The spring presets (default/snappy/gentle) are Go-side damped-oscillator physics with no CSS counterpart, so they live only in theme.json's motion parameters. Under the OS reduce-motion preference the theme emits the same scale with every duration zeroed; the sheet is generated from the non-reduced scale.

Reproducibility

theme.json records the seed (hex plus its OKLCh hue and sat), the pinned role hexes per mode, the faces, the base radius, the measured lightness scales, both density settings' metrics, the elevation model (surface steps and shadow dps per level) and the motion set (durations, easings, springs). FromSeed(seed) regenerates every ramp and pin from the seed alone — a round-trip test in theme/export asserts it — so the file, not this text, is the contract. To rebrand, rerun vg-tokens -seed #rrggbb; every page here reflows because nothing in them is hard-coded.

Fonts: the tokens name Roboto (and Roboto Mono for code); the pages fall back to system faces when they are not installed. An increased-contrast palette variant exists Go-side (tokens.FromSeedHighContrast, driven by the OS contrast preference); it is not part of this export.

About

The Vibrant Gio design bundle published at claude.ai/design

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages