Design system for pages with no brand
Unbranded covers explainers, small tools, reports, and forms that are not Greenville Triumph or Sean Tippen work. Link three files at a pinned version, pick one of four presets, and build only from the classes and tokens on this page.
Head block
Paste this into the page head, set data-preset on the html element, and fill the brief card before writing any markup. The preloads change with the preset: load that preset's display and body files.
<!doctype html>
<html lang="en" data-preset="standard">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="design-system" content="unbranded 1.0.0">
<title>Literal page title</title>
<link rel="preload" as="font" type="font/woff2" crossorigin href="https://unbranded.seantippen.com/1.0.0/fonts/schibsted-grotesk-latin-wght-normal.woff2">
<link rel="preload" as="font" type="font/woff2" crossorigin href="https://unbranded.seantippen.com/1.0.0/fonts/public-sans-latin-wght-normal.woff2">
<link rel="stylesheet" href="https://unbranded.seantippen.com/1.0.0/tokens.css" integrity="sha384-XDeiGpxMH40GoET+QMRWCAR5eq1KmvHub/TMCLGlumygsWgGzX7BB89qEK7gSAF2" crossorigin="anonymous">
<link rel="stylesheet" href="https://unbranded.seantippen.com/1.0.0/components.css" integrity="sha384-SKo2Dp7MntS9/kGGp647kNZZWg7YuCN3WaxZb1oIG+mvmBq+bEnXhadFdj5L+qLP" crossorigin="anonymous">
<script defer src="https://unbranded.seantippen.com/1.0.0/ub.js" integrity="sha384-KBr0JNa28XlPRogXFivF133uGU0fVJDWyEt7mG+uwMHPlPQoJ3DYPmG4iAtD242u" crossorigin="anonymous"></script>
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<!-- ub-brief
purpose: One sentence saying what this page is for.
reader: Who reads it and what they already know.
must-make-clear: The one thing the reader must leave knowing.
preset: standard
starter: argument
signature-move: inverse band
sources: Where every number on the page comes from.
-->
</head>
This release
- Version
- 1.0.0
- Released
- November 1, 2026
- Files
- tokens.css, components.css, and ub.js
- Presets
- 4: standard, tool, reading, and report
- Schemes
- Light by default; dark and auto on request
- Contrast
- 192 color pairs pass WCAG 2.2 and APCA
- Contract
- SKILL.md
- Registry
- registry.md and registry.json
- Current version
- versions.json
Pin the exact version
There is no floating latest URL. Every file lives in an immutable version folder, so a page keeps working when a new version ships.
Presets
A preset sets fonts, accent, neutrals, radii, density, and motion together. Pick the one that matches the job of the page; never mix fonts or accents across presets. Each column below is rendered in its own preset.
| Setting | standard | tool | reading | report |
|---|---|---|---|---|
| Heading | Permit backlog by district | Permit backlog by district | Permit backlog by district | Permit backlog by district |
| Body | Open requests rose for six straight weeks, mostly in water billing. |
Open requests rose for six straight weeks, mostly in water billing. |
Open requests rose for six straight weeks, mostly in water billing. |
Open requests rose for six straight weeks, mostly in water billing. |
| Actions | ||||
| Input | ||||
| Status and link | Selected Open request | Selected Open request | Selected Open request | Selected Open request |
| Used for | General pages, explainers, demos, light tools | Small apps, admin screens, data entry | Long reads, essays, documentation | Reports, tables, finance and operations summaries |
| Accent | Orange, hue 58, dark label | Petrol, hue 218, white label | Berry, hue 353, white label | Ink, near black |
| Neutrals | Cool steel, hue 235 | Warm gray, hue 85 | Green gray, hue 160 | Warm gray, hue 60 |
| Display face | Schibsted Grotesk 700 | Funnel Display 600 | Literata 650 | Source Serif 4 600 |
| Body face | Public Sans | Public Sans | Literata; Public Sans for UI | Public Sans |
| Radius | 4, 6, and 8 px | 3, 4, and 6 px | 4, 4, and 8 px | 2, 2, and 4 px |
| Density | Regular | Compact | Comfortable | Regular |
| Overlay shadow | Tinted, light | None, border only | Tinted, light | None, border only |
| Starter | argument and form | tool | article | report |
Attributes
| Attribute | Values | Default | Changes |
|---|---|---|---|
data-preset | standard, tool, reading, report | standard | Colors, fonts, radii, density, motion, and measure |
data-scheme | light, dark, auto | light | Light or dark values of every color |
data-density | compact, regular, comfortable | The preset's | Control heights, UI text size, and spacing |
data-contrast | more | Off | Stronger borders, focus ring, and muted text |
Any of the four works on any element, so one section of a page can switch preset or scheme. The Rules section below is an example: it sets data-scheme to the opposite of the page.
Color
Pages use role tokens only. Values are generated, then checked against both contrast gates before they ship. The value column shows what the current preset and scheme resolve to; switch either at the top of the page.
Surfaces and text
| Token | Color | Value | Use |
|---|---|---|---|
--ub-bg | Page ground | ||
--ub-surface | Panels | ||
--ub-surface-raised | Inputs, popovers, and menus | ||
--ub-surface-sunken | Wells, code, and sunken bands | ||
--ub-surface-overlay | Dialogs and drawers | ||
--ub-fg | Body text and headings | ||
--ub-fg-muted | Labels, captions, and metadata | ||
--ub-fg-faint | Placeholders and disabled text | ||
--ub-border | Separators and panel edges | ||
--ub-border-strong | Header rules and emphasis | ||
--ub-border-control | Input and checkbox edges (3:1) |
Accent
| Token | Color | Value | Use |
|---|---|---|---|
--ub-accent | One primary button per view | ||
--ub-accent-hover | Primary button on hover | ||
--ub-on-accent | Label on the accent fill | ||
--ub-accent-subtle | Selected rows, accent band | ||
--ub-accent-border | Edge of a selected item | ||
--ub-accent-text | Links and selected text | ||
--ub-focus | Focus ring |
Never use the accent for headings, body text, ordinary borders, decorative icons, a chart series, or a second button in the same view.
Status
| Status | Solid | Subtle | Border | Text | On solid | Example |
|---|---|---|---|---|---|---|
| success | Closed | |||||
| warning | Overdue | |||||
| danger | Failed | |||||
| info | Scheduled |
Status colors are the same in every preset and appear only in status UI, always with an icon and a word. Warning yellow is never decoration.
Data
Categorical, --ub-chart-1 to -8. Separated for color-vision deficiency; each is 3:1 on the surface. Slot order moves a hue that is close to the accent to the end.
Sequential, --ub-seq-1 to -7
Diverging, --ub-div-1 to -9, blue to orange through a neutral midpoint. Red and green are never paired.
Neutral scale, --ub-neutral-1 to -12. Read-only: components use it, pages use the role tokens.
Accent scale, --ub-accent-1 to -12. Read-only.
Contrast gates
| Pair | WCAG 2.2 | APCA Lc |
|---|---|---|
| Body text on page and surfaces | 7:1 | 90 |
| Muted text, links | 5.5:1 | 72 |
| Faint text, status text | 4.5:1 | 60 |
| Label on a solid fill | 4.5:1 | 60 |
| Control border | 3:1 | 30 |
| Focus ring | 3:1 | 45 |
| Chart color on surface | 3:1 | Not used |
Type
UI sizes are fixed; content sizes scale between a 320 px and a 1440 px viewport. Fonts are self-hosted with tokens.css, so a page needs no Google Fonts link. The face below follows the preset chosen at the top.
| Token | At 320 px | At 1440 px | Used for | Sample |
|---|---|---|---|---|
--ub-text-5xl | 47.8 | 68.7 | Display, one short line | Backlog |
--ub-text-4xl | 39.8 | 54.9 | Display | Backlog |
--ub-text-3xl | 33.2 | 43.9 | Page heading (h1), stat value | Backlog |
--ub-text-2xl | 27.6 | 35.2 | Section heading (h2) | Backlog |
--ub-text-xl | 23.0 | 28.1 | h3 | Backlog |
--ub-text-lg | 19.2 | 22.5 | h4, lead, reading body | Backlog |
--ub-text-base | 16.0 | 18.0 | Body | Open requests by team |
--ub-text-sm | 14.0 | 14.0 | Dense UI, tables, captions | Open requests by team |
--ub-text-xs | 12.0 | 12.0 | Badges, source lines (the floor) | Open requests by team |
Fonts
| Role | standard | tool | reading | report | Token |
|---|---|---|---|---|---|
| Display | Schibsted Grotesk | Funnel Display | Literata | Source Serif 4 | --ub-font-display |
| Body | Public Sans | Public Sans | Literata | Public Sans | --ub-font-body |
| UI and tables | Public Sans | Public Sans | Public Sans | Public Sans | --ub-font-ui |
| Code | Atkinson Hyperlegible Mono | Atkinson Hyperlegible Mono | Atkinson Hyperlegible Mono | Atkinson Hyperlegible Mono | --ub-font-mono |
Body text runs at --ub-measure (62 to 72 characters by preset). Headings balance and stop at 28 characters wide. Space above a heading is three times the space below it. Tables use tabular figures.
Space and layout
Spacing steps are multiples of 4 px with the same numbers Tailwind uses. Breakpoints are two literals, 40rem and 64rem; components use container queries.
| Token | px | Size |
|---|---|---|
--ub-space-1 | 4 | |
--ub-space-2 | 8 | |
--ub-space-3 | 12 | |
--ub-space-4 | 16 | |
--ub-space-5 | 20 | |
--ub-space-6 | 24 | |
--ub-space-8 | 32 | |
--ub-space-10 | 40 | |
--ub-space-12 | 48 | |
--ub-space-16 | 64 | |
--ub-space-20 | 80 | |
--ub-space-24 | 96 |
| Token | Phone | Desktop | Use |
|---|---|---|---|
--ub-gutter | 16 | 40 | Page side padding |
--ub-gap | 16 | 28 | Grid and sidebar gaps |
--ub-block | 24 | 48 | Between blocks |
--ub-section | 48 | 112 | Between sections |
--ub-control-md | 44 | 40 | Control height (touch is taller) |
--ub-page-narrow | 640 | 640 | Forms, single columns |
--ub-page-default | 1152 | 1152 | Content and tools |
--ub-page-wide | 1536 | 1536 | Data-heavy pages |
Layout primitives
Layout comes from these classes, not from page CSS. Each one takes a few custom properties (knobs) for its sizes.
| Class | Does | Knobs |
|---|---|---|
.ub-stack | Vertical rhythm for controls and panels | --ub-stack-space |
.ub-flow | Prose rhythm, more space above headings | --ub-flow-space |
.ub-center | Centered column with the page gutter | --ub-center-max |
.ub-cluster | Wrapping row of buttons, tags, or metadata | --ub-cluster-space |
.ub-grid | Two tracks, 7 to 5 by default; one column under 40rem | --ub-grid-cols, --ub-grid-min |
.ub-with-sidebar | Sidebar that drops under the content when narrow | --ub-sidebar-width |
.ub-switcher | Row that becomes a column below a width | --ub-switcher-threshold |
.ub-cover | Full-height screen with one centered block | --ub-cover-min |
.ub-frame | Crops media to an aspect ratio | --ub-frame-ratio |
.ub-reel | Horizontal scroller for screenshots | --ub-reel-item |
.ub-band | Full-width section with its own ground | data-ground, data-scheme |
Components
One root class per component, parts named after it, and closed attributes for variants. States come from the browser (hover, focus, disabled, checked) and ARIA, never from state classes. The figures in this section are sample data generated for this page.
Button
| Variant | Default | Hover | Focus | Pressed | Disabled | Busy |
|---|---|---|---|---|---|---|
| solid | ||||||
| soft | ||||||
| outline | ||||||
| ghost | ||||||
| link | Not used |
One solid accent button per view. Neutral and danger tones, sizes, a pressed toggle, icon buttons (each with an aria-label), and a tooltip on hover or focus.
Forms
Panel, description list, and callout
Request 2213
OverdueHigh bill dispute, opened 4 days ago
- Team
- Water billing
- Address
- 418 Larkin Mill Road, Unit 2
- Caller
- Mara Okafor
- Amount disputed
- $186.40
- Due
- Oct 30, 2026
Billing cycle closes Friday
Disputes opened after Friday move to the November cycle.
Meter re-read scheduled
A technician visits 418 Larkin Mill Road on Nov 3.
2 days past due
Overdue requests go to the team lead each morning.
Payment sync failed
The billing export did not finish. Run it again from Settings.
Stats, table, and chart
657
Open requests
All teams, Nov 1, 2026
546
Opened this week
29 more than last week
528
Closed this week
17 more than last week
3.0
Median days to close
Across 5 teams with closures
Source: sample data generated for this page.
Requests opened and closed per week
All teams, 12 weeks from Aug 10 to Oct 26, 2026
- Opened
- Closed
Source: sample data generated for this page.
| Permits | 214 | 96 | 4.5 |
|---|---|---|---|
| Water billing | 187 | 141 | 2.0 |
| Streets and sidewalks | 162 | 58 | 9.5 |
| Parks | 71 | 44 | 3.0 |
| Records | 23 | 37 | 1.5 |
| Code enforcement and property maintenance | 0 | 12 | No data |
Sort any column by its header. Days is the median number of days to close. The selected row shows the selected state.
Badges
Overlays
Tabs and accordion
- Opened
- 546
- Closed
- 528
- Opened
- 2,092
- Closed
- 2,031
- Opened
- 5,736
- Closed
- 5,505
Can a page change the accent?
No. Pick the preset whose accent fits. A white-label demo overrides the accent through the generator, which checks contrast again.
Does a page work without ub.js?
Yes. The script adds keyboard tabs, sorting, toasts, and the chart bridge. Everything else is CSS and HTML.
How do I update to a new version?
Change the version in the three URLs and the meta tag, then replace the integrity hashes from versions.json.
Empty, loading, and progress
No requests match these filters
Clear the filters to see all 657 open requests.
Navigation, code, and keys
<button class="ub-button" type="button">Save changes</button>
<button class="ub-button" data-variant="outline" data-tone="neutral">Cancel</button>Press Ctrl K to search. Inline code looks like --ub-accent.
Every class
| What it is | Also called | |
|---|---|---|
.ub-stack | Vertical rhythm for components and forms. | None |
.ub-flow | Prose rhythm: space between blocks, more above headings than below. | None |
.ub-box | A bordered padded area. | None |
.ub-center | Centered content column with the page gutter. | None |
.ub-cluster | Wrapping row of items. | None |
.ub-with-sidebar | Sidebar beside content that wraps under it when narrow. | None |
.ub-switcher | Row that becomes a column below a container width. | None |
.ub-cover | Fills the viewport height with one centered main block. | None |
.ub-grid | Two-track grid with one dominant track (7/5); equal tracks only with data-fit. | None |
.ub-frame | Crops media to an aspect ratio. | None |
.ub-reel | Horizontal scroller. | None |
.ub-with-icon | Icon aligned to the first line of text. | None |
.ub-cq | Marks a container for container queries. | None |
.ub-table-wrap | Scroll container for wide tables. | None |
.ub-band | Full-width page section that carries its own ground. | None |
.ub-display | Display-size headline for one short phrase (six words at most). | None |
.ub-lead | The paragraph that says what the page is for. | None |
.ub-icon | Inline SVG icon from the page's Lucide sprite. | None |
.ub-button | The action control. | btn, cta |
.ub-icon-button | A button that shows only an icon. | icon-btn |
.ub-field | A label, a control, help text, and an error message. | None |
.ub-input | Text input, select, or textarea. | text-field, textbox |
.ub-check | Checkbox or radio with its label. | checkbox, radio |
.ub-switch | On and off for one setting that applies immediately. | toggle |
.ub-segmented | A small set of mutually exclusive views or values. | segmented-control, toggle-group |
.ub-fieldset | Groups related fields under a legend. | None |
.ub-panel | Groups one task's content with a title and optional actions. | card, box, tile |
.ub-dl | Label and value pairs, value next to its label. | description-list, key-value |
.ub-callout | A message with a status: full border, tinted ground, icon, and a word. | alert, banner, notice |
.ub-table | Data table sized to its contents, headers on one line, numbers right-aligned. | None |
.ub-sort | The button inside a sortable column header. | None |
.ub-stat | One sourced figure with a label, an optional change, and its basis. | metric, kpi |
.ub-stats | A row of two to four stats divided by hairlines. | None |
.ub-figure | A figure, diagram, or image with a caption and a required source line. | image, diagram |
.ub-badge | A short status or count. | tag, pill, label, chip |
.ub-chart | The frame every chart sits in: title, subtitle with units, plot, legend for two or more series, source line. | chart-frame, graph |
.ub-map | Frame for a Leaflet map on OpenFreeMap tiles. | None |
.ub-tip | A short CSS tooltip from data-tip. | tooltip |
.ub-popover | Floating content opened by a button (popovertarget). | None |
.ub-dialog | A modal dialog on the native dialog element. | modal |
.ub-drawer | A dialog attached to one side of the screen. | sheet, tray, side-panel |
.ub-toast | A short confirmation that disappears on its own. | snackbar, notification |
.ub-tabs | Peer views of one dataset. | None |
.ub-accordion | Sections that open one at a time, on details name. | None |
.ub-empty | What is missing, what to do, and one button. | empty-state, zero-state |
.ub-skeleton | A static placeholder block while content loads. No shimmer. | None |
.ub-spinner | The only infinite animation in the system. | None |
.ub-progress | Determinate or indeterminate progress, or a meter. | None |
.ub-appbar | The full-width bar at the top of a page or app. | header, navbar, topbar |
.ub-nav | A list of page links. | None |
.ub-sidenav | A vertical list of sections or pages. | None |
.ub-app | App shell: appbar on top, sidebar beside main. | None |
.ub-toolbar | A row of controls above a table or view. | None |
.ub-code | A code block with a copy button. | None |
.ub-skip | Skip link to the main content. | None |
.ub-num | Right-aligned tabular figure. | None |
.ub-nowrap | Keeps a value on one line. | None |
.ub-sr-only | Visually hidden, still read by screen readers. | visually-hidden |
.ub-muted | Secondary text color. | None |
Rules every page follows
These are the checks ub-check runs. Floor rules never have an exception. A Reflex rule can be waived once per page with a written reason.
| ID | Rule |
|---|---|
| F01 | Head block present, files pinned to an exact version |
| F02 | No unknown ub- class |
| F03 | No unknown --ub- token |
| F04 | No other design system on the page |
| F05 | No color literal outside tokens.css |
| F06 | Fonts only through --ub-font-* |
| F07 | No gradients and no gradient text |
| F08 | No backdrop blur |
| F09 | No scale on hover, press, or focus |
| F10 | No panel or box inside another |
| F11 | No colored stripe on one side of a box |
| F12 | No icon tile above a heading |
| F13 | No spaced-out uppercase labels |
| F14 | No emoji outside content |
| F15 | No em-dashes |
| F16 | No placeholder text, names, or image hosts |
| F17 | No accent bar under or beside a heading |
| F18 | No background image or pattern on the page or a section |
| ID | Rule |
|---|---|
| R01 | Spacing, radius, and z-index from tokens |
| R02 | Headings and paragraphs aligned left |
| R03 | No row of three or more identical cards |
| R04 | No 01, 02, 03 section markers |
| R05 | The spinner is the only infinite animation |
| R06 | No bounce or elastic easing |
| R07 | No !important in page CSS |
| R08 | Every CDN file pinned and hashed |
| R09 | Charts colored only through the bridge |
| R10 | Warning colors only in status UI |
| R11 | Brief card in the page head |
| R12 | A figure with %, x, or + has a source |
| R13 | No numbered scale tokens in page CSS |
| R14 | One solid accent button per section |
Copy
Plain, literal, and short. Name the thing; say what happened; say what to do.
| Element | Rule | Example |
|---|---|---|
| Page headline | What the page is or does, under 10 words | Open requests by team |
| Section heading | Name the contents in one to three words | Overdue requests |
| Button | Verb and object, sentence case, three words or fewer | Export CSV |
| Never on a button | Submit, OK, Click here, Get started, Learn more | Read the fee schedule |
| Empty state | What is missing, what to do, one button | No invoices yet. Add one to start tracking. |
| Error | Field, problem, fix; no apology or blame | Email address must include @. |
| Success | Past tense, names the object, no exclamation | Request 2213 assigned to Permits. |
| Destructive confirm | Title names the object; the button repeats the action | Delete 3 requests? |
| Loading | Say what is loading | Loading requests |
| Numbers | Units in headers, one date format, a source for every figure | Revenue (USD); $15/match |
| Terms | One noun per thing across a product | Request everywhere, never ticket or case |
Also banned: em-dashes, buzzwords (seamless, supercharge, world-class, unlock, elevate, streamline, empower), aphorism cadence such as "Less X. More Y.", invented numbers, a subtitle that repeats the title, lorem ipsum, and the brand claiming feelings. Serial lists take the Oxford comma.
Starters
Five page structures, so pages built on the system do not all share one shape. Copy the starter that fits, then replace the sample content. None has a hero, a card row, testimonials, pricing, or an FAQ block.
| Starter | Preset | Structure |
|---|---|---|
| argument | standard | Headline that states the claim, evidence beside it in a 7 to 5 split, sections on alternating grounds, sourced figures |
| article | reading | One column at the prose measure, contents list, notes in the margin on wide screens |
| tool | tool | App shell: top bar, sidebar, toolbar, table, and a detail drawer |
| report | report | Title block with as-of date and source, up to four stats, tables sized to content, a chart, and notes |
| form | standard | Narrow page, fieldsets, inline validation, an error summary, and a confirmation state |
Examples
Three finished pages built from the starters. The explainer and the report use real public data with citations; the tool uses sample data.
| Example | Preset | Data |
|---|---|---|
| How to tell three penguin species apart | standard | Palmer Penguins, CC0 |
| Freezer samples | tool | Sample data |
| Renewable share of electricity, 2000 to 2025 | report | Our World in Data, CC BY 4.0 |
Signature moves
Every page picks one and records it in its brief card: an oversized sourced figure, an inverse band, an accent band, a side-by-side comparison table, a margin column, a large index list used as navigation, an annotated diagram of the page's own mechanism, or a real screenshot at native size.
Files
Every file in a version folder is immutable and served with Access-Control-Allow-Origin: *, so pages on any domain can link it. Client demos copy the folder into their own deploy instead of linking this host.
| File | Pinned URL | Integrity |
|---|---|---|
| tokens.css | https://unbranded.seantippen.com/1.0.0/tokens.css | sha384-XDeiGpxMH40GoET+QMRWCAR5eq1KmvHub/TMCLGlumygsWgGzX7BB89qEK7gSAF2 |
| components.css | https://unbranded.seantippen.com/1.0.0/components.css | sha384-SKo2Dp7MntS9/kGGp647kNZZWg7YuCN3WaxZb1oIG+mvmBq+bEnXhadFdj5L+qLP |
| ub.js | https://unbranded.seantippen.com/1.0.0/ub.js | sha384-KBr0JNa28XlPRogXFivF133uGU0fVJDWyEt7mG+uwMHPlPQoJ3DYPmG4iAtD242u |
| File | Holds |
|---|---|
| SKILL.md | The contract for agents: the build loop, hard rules, head block, decision tables, and snippets |
| registry.md | Every component with its use, classes, variants, states, and knobs |
| registry.json | The same, plus every token name, for tools |
| versions.json | The current version and the integrity hash of each file |
| adapters/shadcn.css | Maps shadcn/ui variables and slots to Unbranded tokens for React apps |
| adapters/tailwind-theme.css | Tailwind v4 theme that replaces the default palette with tokens |
| favicon/ | The default icon set for unbranded pages |