Work · Repository
polarize-ui
Polarize's design system for scientific interfaces: tokens, zero-build CSS, evidence badges, shadcn/ui + visx React components. Live Storybook ↓
- TypeScript
- MIT licence
- 1 post
- 5 merged PRs
Posts about it
README
The design system behind Polarize’s research tools. It is built for interfaces that show scientific data honestly: every measured, modelled or speculative value carries a label, and the label can never be hidden.
A label may never be hidden. Its explanation must be.
It comes in two forms that share one set of tokens:
- Zero-build CSS + custom elements — link a stylesheet, no npm, no bundler.
- React — shadcn/ui components on the same theme, plus publication layouts, evidence badges and visx charts.
How we choose and judge a view for scientific data (the question first, five things every view states, forms for common data, simulation views, demo-data rules): METHOD.md.
What is in it
Science: the charts and analysis views (the point of the library)
| Charts | LineChart, BarChart, ScatterPlot (least-squares fit with a labelled 95% confidence or prediction band, fitted only over the measured range, with n, slope interval, r, R² and p printed; linearFit() gives the same numbers without the chart). A threshold is drawn on the axes it judges, a log axis says so in its label, thinning a trace for display is printed on the panel, and a shared or independent y-scale across panels is declared. |
| Signals | Comodulogram + computeComodulogram(): phase–amplitude coupling (Tort MI) with a family-wise surrogate threshold, significant cells outlined, and unresolvable cells hatched instead of shown as zero. Heatmap (spectrograms, time–frequency maps) on a one-hue magnitude ramp with a colour bar, hover readout, and counted clipping. Waveform with marked regions. |
| Statistics | NullDistribution and nullTest(): an observed statistic against its surrogate or permutation null, with the permutation p printed with its count, z beside it, and a warning when p is at its floor. |
| Evidence | Tier (a MEASURED / PREDICTED / REFUTED badge that can’t be hidden), Value (measured and predicted never styled alike), Readout (figures with their source), and Note + DocsProvider (explanations in a drawer). |
Source: src/science/. Every view is in the live Storybook under Science.
General UI
tokens.json |
The single source of truth: colour, type, spacing, the tier ladder, a colour-blind-validated categorical palette and a one-hue sequential ramp. |
design.css + design.js |
The zero-build form, generated from the tokens: link it and set <body class="ui">. Custom elements: <ui-tier>, <ui-note>, <ui-docs>. |
publication.css |
Layouts for a research blog: sidebar shell, post list, article, prose, source cards, citations, claims, references, pager, footer. React versions render the same classes. |
landing.css |
A lab’s front door: top bar, hero, section heads, process steps, project features, repository cards, a changelog feed, article and story cards, archival plates, the principle block and the site footer. React: src/components/landing.tsx. |
trajectory3d.js |
A path through three dimensions with a marker at the current time, on a plain canvas: mountTrajectory3D(host, options). Drag to turn, pinch to zoom. No dependencies; colour from the tokens. See INTEGRATION.md § 5. |
network3d.js |
An interactive 3-D node-link network on a plain canvas: mountNetwork3D(host, options). The caller supplies geometry and visual state; the component supplies projection, orbit, zoom and depth sorting. |
docs.css + docs.js |
A documentation site for a library or tool: top bar with ⌘K search, left nav and “on this page” column, an always-dark code window, the example panel (one real call with its code, input and output plots), an API reference entry, package and dataset cards, callouts and chips. docs.js wires it through data-ui-* attributes and exports renderPlot / highlight. React: src/components/docs.tsx. How to build one for an existing repository: DOCS-SITES.md. |
specimens.css |
Field-guide style illustrations drawn in theme colours (SPECIMEN-ICONS.md). |
src/components/ |
shadcn/ui components restyled to the type rules, typography (Display, Eyebrow, Standfirst), the publication layouts and Specimen. |
fonts/, icons/ |
Self-hosted Instrument Serif, Inter, IBM Plex Mono, Literata (OFL) and a Phosphor icon sprite (MIT). |
Using it
It is not published to npm. If there is enough interest we will happily turn it into a proper npm package. Until then, use it straight from this repository, always pinned to a release tag, or fork or clone it and make it your own.
In a React app (Vite + Tailwind v4)
npm install github:polarizetech/polarize-ui#v0.5.2 # or: yarn add github:polarizetech/polarize-ui#v0.5.2
/* your app's main stylesheet */
@import "tailwindcss";
@import "tw-animate-css";
@import "@polarizetech/polarize-ui/theme.css";
@source "../node_modules/@polarizetech/polarize-ui/src";
import { Heatmap, NullDistribution, LineChart, Tier, Button } from "@polarizetech/polarize-ui/react"
- The package ships TypeScript source and your bundler compiles it; there is no build step
here. It is type-checked with
noUnusedLocals/noUnusedParameterson, so it compiles under strict app settings. - Dark mode is a
.darkclass on<html>. - Importing from
/reactalso loads the self-hosted fonts.
In a plain HTML page
Add it as a git submodule pinned to a tag, and link the files:
git submodule add https://github.com/polarizetech/polarize-ui.git design
git -C design checkout v0.5.2
<link rel="stylesheet" href="https://github.com/polarizetech/polarize-ui/blob/main/design/design.css">
<link rel="stylesheet" href="https://github.com/polarizetech/polarize-ui/blob/main/design/publication.css"> <!-- optional: blog layouts -->
<script type="module" src="https://raw.githubusercontent.com/polarizetech/polarize-ui/main/design/design.js"></script>
<body class="ui">
The full wiring guide, including how to serve the fonts, is INTEGRATION.md.
Staying up to date
- Watch → Custom → Releases on this repository to be told when a new version is out. Each release has notes listing what changed.
- To update, change the tag you pin: the
#v…in your dependency, orgit -C design checkout v…for a submodule. - Read the release notes before crossing a minor version while we are below 1.0 (see below).
Versioning
Releases follow Semantic Versioning. While the version is below
1.0.0, the rule is the one npm applies to 0.x versions:
| bump | below 1.0.0 | from 1.0.0 on |
|---|---|---|
patch (0.5.2 → 0.5.3) |
never breaking | never breaking |
minor (0.5.x → 0.6.0) |
may be breaking: read the release notes | new features, never breaking |
| major | — | breaking |
Covered by that promise (the public API):
- everything exported from
@polarizetech/polarize-ui/react, and its props; - the stylesheet entry points (
design.css,publication.css,specimens.css,theme.css) and theui-*class names and modifiers they define; - the CSS custom properties (
--background,--space-4,--tier-measured, …) and the keys intokens.json; - the custom elements in
design.jsand their attributes.
Not covered: exact colour values and spacing (a tweak is a patch release, not a breaking
change), the stories, the build scripts, and anything under .storybook/ or .github/.
Removing or renaming anything in the public API always produces a breaking bump:
scripts/api_surface.py lists that surface, and the release workflow compares it with the
previous release. What was removed is listed at the top of the release notes. Changes it
cannot see, such as a prop’s type or a component’s behaviour, have to be marked by whoever
makes them, and the contributor guidance (AGENTS.md) requires it.
Every push to main is released automatically by
.github/workflows/release.yml, which applies these rules:
a commit whose subject has !: (for example refactor!: rename Label to Eyebrow) or a
BREAKING CHANGE: line produces a breaking bump, and anything else produces a patch. Setting
the version in package.json by hand always wins.
Working on it
npm install
npm run storybook # http://localhost:6006
python3 dev_check.py # tokens, contrast, the type rule, generated-file drift
Edit tokens.json, never design.css; regenerate with python3 build_css.py and
python3 shadcn/build_shadcn.py. src/ uses relative imports only. An @/ import would
resolve against the consuming app’s alias, and dev_check.py rejects it. The shadcn CLI writes
@/ imports, so rewrite them after npx shadcn add.
The Storybook’s own interface follows the viewer’s light or dark setting, and so does its
Theme toolbar button. The MCP add-on (@storybook/addon-mcp) lets coding agents find the
components and their stories.
Where it is used
These Polarize repos track new releases automatically. A daily workflow in each one moves its pin to the newest tag, runs that repo’s own checks, and commits and deploys only if they pass.
| repo | pin | gate before it commits |
|---|---|---|
| polarizetech/polarize.tech | design/ submodule |
check_design.py, validate_posts.py |
| joshuaanderton/joshuaanderton.ca | package.json git dependency |
tsc -b && vite build, then the Pages deploy |
| polarizetech/audio-projects | tools/design submodule |
tools/design/dev_check.py |
Background and design decisions: CLAUDE.md. React specifics:
shadcn/SHADCN.md.
Licence
Code: MIT (see LICENSE). Fonts: SIL Open Font License, texts in fonts/.
Icons: Phosphor, MIT.
Changelog
Add reusable 3D node-link network view
Summary
- add a dependency-free mountNetwork3D renderer with orbit, zoom, depth sorting, token colours, and accessible keyboard controls
- ship public TypeScript declarations and integration documentation
- add a synthetic Storybook example and API/design checks
Verification
- python3 dev_check.py: 507/507 passed
- TypeScript: passed
- API surface removal check against v0.6.0: passed
This is an additive public API. It contains only a standard node-link visualization and synthetic story data; Neural Field Lab’s model remains in world-seeding.
feat!: remove the cell field; the homepage story is the shipped page
Removes the animated cell field (<ui-cellfield>, CellField, ./cellfield.js, .ui-cellfield*) at the operator’s request, and rebuilds the Landing homepage story as the page polarize.tech now ships (fieldguide). Adds an optional media prop to ProjectFeature.
Breaking. api_surface.py --removed v0.5.19 lists exactly the cell field’s surface. No consumer uses it: polarize.tech dropped it in v0.5.19; joshuaanderton.ca and the monorepo never used it.
Checks: dev_check 491/491, build_css/build_shadcn –check, tsc; Storybook homepage story renders with no errors.
🤖 Generated with Claude Code
feat: fieldguide theme — white, magenta, Geist
Opt-in fieldguide theme (white page, magenta accent, Geist sans) and additive landing components for polarize.tech’s restyle: .ui-action, .ui-stats, .ui-steps--rows, .ui-topics, .ui-specimen-tile, .ui-plate--ink, and the crab/neuron/magnet specimens.
Non-breaking: existing themes render unchanged (cards read --landing-radius/--landing-fill with the old values as fallbacks); api_surface.py --removed v0.5.18 is empty. dev_check 497/497, build_css/build_shadcn –check, tsc pass.
🤖 Generated with Claude Code
feat: trajectory3d.js, a 3-D path with a time marker (zero-build)
What
mountTrajectory3D(host, options) draws a path through three dimensions on a plain 2-D canvas, with a marker at the current time and the last trail seconds drawn heavier. It returns {update, setTime, show, resetView, destroy}. Zero-build, no dependencies, same shape as cellfield.js (a mount* function plus a .d.ts).
trajectory3d.js+trajectory3d.d.tsat the repo rootpackage.json: shipped infiles, new entry point./trajectory3d.jsdev_check.py:check_trajectory3d();scripts/api_surface.pytracks the export and the handle methods, so a later rename forces a breaking bump- Story: Science / 3-D views / Path with a time marker (zero-build, synthetic)
- Docs:
INTEGRATION.md§ 5 (and the allow-list line in § 1), a row inREADME.md
Versioning
Additive, so a patch. A new entry point and nothing existing changes. The React Trajectory3D (the hodogram on Scene3D) is a separate view and is untouched; no React wrapper was added for this module, to avoid a name collision.
Public-repo rule
- Method: a 3-D state-space path / phase portrait with a playhead. Standard.
- Data: the story integrates the Lorenz system (Lorenz 1963) in the story itself. Synthetic, and labelled so.
- Findings: none shown or stated.
Differences from the private copy it was ported from
- Axes are right-handed and positive pitch looks down, as in
src/science/space(the original was left-handed, seen from below). Paths appear mirrored relative to the original. - On a drag the near side follows the pointer (direction reversed).
- A two-finger touch pinch zooms. Before, only Ctrl/Cmd-scroll did.
- Arrow keys turn and
+/-zoom when the canvas has focus. - Redraws on a theme change; falls back to
--chart-1/--chart-4on the React theme.
Checked
python3 dev_check.py: 429/429npx tsc -p .: cleanpython3 scripts/api_surface.py --removed v0.5.16: nothing removed- In a browser: a zero-build page on
design.cssand the Storybook story, light and dark, and at 375 px (no sideways scroll). Turn, pinch, Ctrl-scroll zoom, keyboard, reset, destroy and live theme switch work; a plain scroll is left to the page. Pinch was tested with synthetic pointer events, not on a real phone.
Note for consumers
The canvas takes every touch that starts on it (touch-action: none), so a page needs scroll room beside or below it on a phone. Documented in INTEGRATION.md.
🤖 Generated with Claude Code
feat: landing layer and cell field
A lab’s front door, in both renderers, drawn from the polarize.tech homepage redesign.
What’s added
landing.css(zero-build) andsrc/components/landing.tsx(React), rendering the same classes:TopNav,Hero,Cta,Section,ProcessSteps,Promises,ProjectFeature,RepoCard,ChangeFeed,FeaturedArticle,ArticleCard,MetaLine,StoryCard/StoryRow/StoryGrid,Plate,Principle,TierLegend,SiteFooter. Responsive to phone width; the top bar folds into a<details>menu with no script.cellfield.js:<ui-cellfield>, a WebGL speck field (clusters at different depths, per-cell polarisation, occasional coordinated waves, an optional hero cell). Decoration only:aria-hidden, no fetch, one still frame underprefers-reduced-motion. React:<CellField>, same engine, typed bycellfield.d.ts.- Token:
radius.xs(4px). - Checks:
dev_check.pyholdslanding.cssto the publication.css rules (generalised ascheck_layer) and checkscellfield.js;scripts/api_surface.pylists both files. - Storybook: General UI/Landing: the full homepage at desktop and phone, plus each part.
Versioning
Additive only: nothing renamed or removed (api_surface.py --removed v0.5.5 is empty), so this is a patch release.
Checks
python3 dev_check.py: 309/309build_css.py --check,build_shadcn.py --check: up to datetsc -p .: clean
polarize.tech adopts it in polarizetech/polarize.tech (branch home-redesign) once this is released.
🤖 Generated with Claude Code