Getting started
Every app loads the primitives and one theme. The rest is optional.
Apps built with npm
Install it from a pinned commit or tag, then import the primitives and the app's theme.
import "obvious/css/primitives.css";
import "obvious/css/themes/core.css";
import "obvious/css/base.css"; // optional
import "obvious/css/components.css"; // optionalApps that don't use npm
The static site and the README card generator carry a copy of scripts/sync-ds.mjs and a generated ds/ folder. ds/.version does the job of a lockfile.
node scripts/sync-ds.mjs # pull main into ds/
node scripts/sync-ds.mjs --ref v0.2.0 # pin a tag
node scripts/sync-ds.mjs --check # CI: fail if ds/ is stale
DS_LOCAL=../obvious node scripts/sync-ds.mjs # from a local checkout<link rel="stylesheet" href="ds/css/primitives.css">
<link rel="stylesheet" href="ds/css/themes/core.css">
<link rel="stylesheet" href="ds/css/base.css">Tailwind v4
@import "tailwindcss";
@import "../../ds/css/primitives.css";
@import "../../ds/css/themes/parchment.css";
@import "../../ds/tailwind/theme.css";The bridge only adds. It gives you bg-surface, text-ink-muted, rounded-ds-lg, ease-ds-out and shadow-ds-md, all reading --ds-* at runtime, and leaves Tailwind's own scales alone. The first version mapped onto --spacing-*, and max-w-md became 16px. Tailwind's spacing scale isn't mapped at all, so use var(--ds-space-md) when you want a step.
It does take over --font-sans and --font-mono, so the app's utilities use the theme's faces.
base.css
Opt-in, and sets no colour or layout. It's the behaviours every app was rewriting: box sizing, focus rings, ::selection, balanced headings, and a reduced-motion block that zeroes delay as well as duration. Staggered entrances lean on delay, so zeroing only the duration leaves people looking at an empty screen.
Components
@import "../ds/css/components.css"; /* all of them */
@import "../ds/css/components/field.css"; /* or just the one */The parts CSS can't do are in one dependency-free module. Each function returns a teardown or a small control object, which is the shape a React effect wants.
import { cycleLoadingText } from "obvious/behaviours";
useEffect(() => cycleLoadingText(ref.current, phrases), []);Tokens in JS
For anything that can't read a stylesheet: the README card, OG images, canvas and WebGL.
import { tokens } from "obvious/tokens";
tokens.themes.core["--ds-color-accent"]; // "#5B7FE8"Working on obvious itself
npm run build # regenerate dist/ and this site
npm run check # fail if dist/ isn't committed up to date
npm run site:serve # this site on localhost:4321dist/ is committed because apps copy it with no build step of their own.