Design system
Uniform Design
Brand tokens, CSS components, self-hosted fonts and the Atmosphere wave motif. One small, versioned package for websites, applications and documents.
Get started All components on one page
-
HTML first. Plain
u-*classes, no framework. - Two themes. Dark by default, light anywhere.
- Strict-CSP ready. No inline styles, no third-party requests.
Install
Install a pinned release. dist/ is committed, so nothing builds on
install.
npm install --save-exact github:UniformChicago/design#v0.1.7
Copy node_modules/@uniform/design/dist/ to your static directory,
keeping its structure (CSS references fonts by relative path), and link the stylesheet:
<link rel="stylesheet" href="/design/design.css" />
Not using npm? Download dist/ from a tag and check each file against
dist/manifest.json.
Usage
Put u-root on the body and compose with native elements. Dark is the
default; set data-theme="light" on any element to switch it and
everything inside.
<body class="u-root" data-theme="light">
<main class="u-panel">
<h1 class="u-title">Your workspace<span class="u-dot" aria-hidden="true"></span></h1>
<p class="u-lede">A clear place to begin.</p>
<a class="u-button" href="/records">View records</a>
</main>
</body>For your own components, use the theme variables and the spacing and radius tokens:
.record-card {
background: var(--u-surface);
color: var(--u-fg);
padding: var(--u-space-5);
border-radius: var(--u-radius-lg);
}Color
The brand palette, from tokens/tokens.json. It's also available as
JavaScript (@uniform/design/tokens) and Python. Consumers never
hard-code hex values.
--u-inkPrimary text on light; letters in the color lockup--u-paperText on dark; light surfaces--u-slateSecondary text on light; lockup descriptor--u-signalThe brand dot and accents on light--u-signal-on-darkSignal tuned for AA on Night--u-lakeLinks and labels on light--u-lake-lightLinks and highlights on dark--u-nightDark background--u-mistSecondary text on dark (Slate tuned for AA on Night)--u-ruleHairlines on light--u-rule-darkHairlines on dark
Theme variables
Build with these, not the palette. They switch with the theme, so each swatch below changes when you switch between dark and light.
--u-bgPage background--u-surfacePanels and cards--u-fgBody text--u-mutedSecondary text--u-linkLinks, primary buttons--u-accentBrand dot, accents--u-lineHairlines--u-hoverHover fill
Type
Two self-hosted families under the SIL Open Font License. No font CDN.
Public Sans
Public Sans
Public Sans
IBM Plex Mono
Spacing and radius
--u-space-1--u-space-2--u-space-3--u-space-4--u-space-5--u-space-6--u-space-7
--u-radius-sm--u-radius-md--u-radius-lg
Typography
The brand dot is always u-dot, never a typed period.
Eyebrow label
Page title
Section heading
A lede introduces the page in one or two sentences.
Inline code: sha256:9f3c72b1
<p class="u-eyebrow">Eyebrow label</p>
<h3 class="u-title">Page title<span class="u-dot" aria-hidden="true"></span></h3>
<p class="u-heading">Section heading</p>
<p class="u-lede">A lede introduces the page in one or two sentences.</p>
<p class="u-meta">Meta text · 2.4 MB · Updated today</p>
<p>Inline code: <code class="u-code">sha256:9f3c72b1</code></p>Panels
Panel
A surface for grouped content.
Accent panel
The accent edge marks the primary panel on a page.
<div class="u-panel">
<p class="u-heading">Panel</p>
<p>A surface for grouped content.</p>
</div>
<div class="u-panel u-panel--accent">
<p class="u-heading">Accent panel</p>
<p>The accent edge marks the primary panel on a page.</p>
</div>Callouts
Audit chain verified: 42 entries.
Audit chain broken at entry 17.
<p class="u-callout">Audit chain verified: 42 entries.</p>
<p class="u-callout u-callout--danger">Audit chain broken at entry 17.</p>Facts
Label and value pairs on a dl, inline or stacked.
- Category
- Agreement
- Size
- 2.4 MB
- Name
- Jordan Rivera
- Service area
- Example City
<dl class="u-facts">
<dt>Category</dt>
<dd>Agreement</dd>
<dt>Size</dt>
<dd>2.4 MB</dd>
</dl>
<dl class="u-facts u-facts--stacked">
<dt>Name</dt>
<dd>Jordan Rivera</dd>
<dt>Service area</dt>
<dd>Example City</dd>
</dl>Tables
The wrapper scrolls on narrow screens. Give it
tabindex="0" role="region" aria-label="…" so keyboard users can reach
it.
| Name | Role | Status |
|---|---|---|
| Jordan Rivera | Example role | Active |
| Sam Lee | Example role | Pending |
<div class="u-table" tabindex="0" role="region" aria-label="Example registry">
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Role</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Jordan Rivera</td>
<td>Example role</td>
<td><span class="u-tag u-tag--ok">Active</span></td>
</tr>
<tr>
<td>Sam Lee</td>
<td>Example role</td>
<td><span class="u-tag">Pending</span></td>
</tr>
</tbody>
</table>
</div>Forms
Wrap or associate every control with its label. Validation and error messages belong to your app.
<div class="u-field">
<label for="ex-deal">Transaction</label>
<input id="ex-deal" type="text" placeholder="e.g. 100 Example St" />
</div>
<div class="u-field">
<label for="ex-category">Category</label>
<span class="u-select">
<select id="ex-category" aria-describedby="ex-category-hint">
<option>agreement</option>
<option>disclosure</option>
</select>
</span>
<span class="u-hint" id="ex-category-hint">Uploads stay in your inbox until finalized.</span>
</div>
<div class="u-field">
<label for="ex-file">File</label>
<input id="ex-file" type="file" />
</div>Empty state
No records yet
Upload a document to start.
<div class="u-empty">
<p class="u-heading">No records yet</p>
<p>Upload a document to start.</p>
</div>Progress and checks
<div class="u-progress" role="progressbar" aria-label="Example progress" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100">
<span class="s-w40"></span>
</div>
<div class="u-check u-check--done">
<span class="u-check__status" aria-hidden="true"></span>
<span>
<span class="u-check__title">Example document</span>
<span class="u-check__detail">Done</span>
</span>
</div>
<div class="u-check u-check--progress">
<span class="u-check__status" aria-hidden="true"></span>
<span>
<span class="u-check__title">Example step</span>
<span class="u-check__detail">In progress</span>
</span>
</div>Atmosphere
The wave motif behind this page. One implementation draws the animated canvas and the static SVG. It pauses for reduced motion and in hidden tabs.
<canvas id="atmosphere" class="u-atmosphere" aria-hidden="true"></canvas>
<script type="module" src="/design/atmosphere.js"></script>Or mount it yourself, and dispose of it when the view unmounts:
import { mountAtmosphere } from "@uniform/design/waves";
const dispose = mountAtmosphere(document.querySelector("#atmosphere"));For SVG output, pass only trusted values; the helpers reject out-of-range sizes and non-hex colors.
Assets
Brand marks ship in dist/svg/, fonts in
dist/fonts/. Use the on-dark variants on dark surfaces.
Releases
Every vX.Y.Z tag becomes a
GitHub Release once the full
checks pass, with notes, the npm tarball, SHA256SUMS and a signed
build-provenance attestation. Upgrade on purpose: change the pinned tag, read the
changelog, then
test.
gh attestation verify uniform-design-0.1.7.tgz -R UniformChicago/designLicensing
Code is Apache-2.0. Uniform names, logos, wordmarks and icons are all rights reserved (LICENSE-BRAND). Fonts are under the SIL Open Font License.