obvious v0.1.0

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";  // optional

Apps 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:4321

dist/ is committed because apps copy it with no build step of their own.