Skip to content
Concepts

Concepts

CSS-first visual-law package for Fungi

Stipe is a CSS-first theme layer for Tailwind CSS v4 and daisyUI 5. One stylesheet gives you a set of maintained daisyUI themes, a named spacing scale, self-hosted fonts, and a small vocabulary of material tokens for frames, shadows, cards and illustration colours.

A few typed helpers sit beside the CSS. They switch and watch the data-theme attribute, project terminal colours for latte and mocha, build the custom properties for the embeddable embed themes, and style Pierre’s file tree component from the same theme.

Stipe stays out of your app’s state. You own theme persistence, storage, events and components. Caps is the React component library built on top of it.

Set a shipped theme with data-theme on the root or on an element.

STIPE_THEMES lists the curated shipped palettes: Daisy light and dark, plus latte, mocha, readout, readout-night, field-guide, field-guide-night, embed, embed-dark, phosphor, codec, pirate-radio, pirate-radio-night, coin, and coin-night. The consulting family adds consulting, consulting-night, consulting-riso-latte, and consulting-riso-glomzy, each with an explicit -ink inverse. A nested consulting theme chooses its own colorway. coin is the arcade cabinet: cream (or, in coin-night, deep purple) paper, a gold primary, ink outlines, hard drops, and buttons that sink into their slot. It is a complete palette; apply it to a page like latte, not to one control. chamfer is a nested finish that inherits the enclosing palette and typography. Button roles remain independent of it. The vishnu finish retains the host palette and supplies Card gradient faces, outline frames, featured shadows, empty-state borders and hover motion. Complete nested palettes reset those values, and plain surface cards keep their ordinary face. The paper finish inherits its enclosing palette and supplies game-window linework, hard shadows, and control depth. A nested complete palette resets material overrides; another partial finish inherits them. The ember and tide tints inherit field-guide’s paper and typography while changing primary and accent colors. Their paired light/dark values follow the enclosing color scheme. Inside that composition, tape selects dark paper, a monospace card body and a red recording rim; sunrise supplies the existing gold rim and wash. Nest these on GameCard to keep the finish local to the end card. Serialized theme choices use stipeThemeSchema (a Zod enum); StipeTheme is its inferred type. Daisy’s other demo themes are not bundled. Hosts can still add a custom palette and apply its name through the DOM helpers.

The generated Stipe API reference lists the typed exports. applyTheme accepts any non-empty Daisy theme name, and getTheme reads the root’s current data-theme value. subscribeTheme watches only that attribute and returns a cleanup function; it safely does nothing where there is no DOM or MutationObserver. Theme changes emit no storage or global events. terminalPalette returns no palette for unknown themes; Pierre and Gooey exports are bounded projections only.

applyTheme(theme, root?) sets data-theme on the root element, or on the element you pass. getTheme(root?) reads it. subscribeTheme(listener, root?) watches only that attribute and returns a cleanup function. These are plain DOM helpers. Stipe has no JavaScript theme provider and no token-object format.

Stipe owns the coral terminal tint (#f98284). terminalPalette("latte") and terminalPalette("mocha") return frozen semantic and ANSI colour sets that fit terminal emulators such as xterm.js. Other theme names return undefined. The Fungi wordmark art lives in lantern.

The stylesheet supplies one rem-based grid through Tailwind’s spacing namespace:

Name Value At a 16px root
2xs 0.125rem 2px
xs 0.25rem 4px
sm 0.5rem 8px
md 0.75rem 12px
lg 1rem 16px
xl 1.5rem 24px
2xl 2rem 32px
3xl 3rem 48px
4xl 4rem 64px

Use classes such as px-xs, py-sm, gap-md and p-lg. Plain CSS can use var(--spacing-sm). Theme changes keep these values. STIPE_SPACING_NAMES exports the names for class-merging configuration, and Caps already uses them. Zero, automatic layout, proportional sizing and measured geometry keep their ordinary CSS meaning. These tokens supply spacing. They do not constrain pane resizing or force content to a fixed height.

The main stylesheet maps Nunito to font-sans and Maple Mono to font-mono. Use the sans voice for interface copy and controls. Keep mono for machine values, code and identifiers. Editorial pages also get Fraunces for font-display, Nothing You Could Do for font-handwritten and Grenze Gotisch for font-nameplate.

Tailwind compilation alone does not load fonts. Hosts with an asset bundler such as Vite import @fungi.computer/stipe/fonts.css to load them. It references the bundled WOFF2 files relative to the Stipe package, so each host’s asset build emits them on its own origin. Nunito ships as separate Latin, Latin Extended, Cyrillic, Cyrillic Extended and Vietnamese subsets that load only for matching text. Normal Nunito weights 200 to 900 and Maple Mono weights 200 to 800 use font-display: swap. No Google Fonts request or runtime font dependency is needed. Font provenance records sources and checksums.

Stipe always emits Tailwind’s --color-black and --color-white endpoints. SVG lighting and alpha masks can consume them even when the host uses no black or white utility classes. Caps owns the geometry and depth calculations.

Stipe defines a few material properties on top of daisyUI’s own --depth, --noise, radius and colour variables. Caps reads them in ordinary component rules, with no selectors for individual themes:

CSS property Value type Default
--stipe-surface-frame-width Nonnegative CSS length Component frame scale: 2/6/10px for sm/md/lg
--stipe-surface-frame-image CSS background image, gradient, or none Frame gradient derived from the semantic palette
--stipe-shadow-raised Box-shadow list or none Component-specific raised shadow
--stipe-shadow-inset Box-shadow list or none Component-specific recessed shadow
--stipe-shadow-edge Box-shadow list or none Component-specific edge highlight
--stipe-display-texture-image CSS background image, gradient, or none none
--stipe-display-text-shadow Text-shadow list or none none

Frame and shadow overrides reset to initial at each data-theme boundary. For these unregistered custom properties, that selects the component’s var() fallback. It does not mean a zero-width frame. none explicitly removes an image or shadow. Stock daisyUI themes do not need to define these properties.

The frame width is decoration, not content padding or a hit target. In Caps, Surface supplies the frame, Screen the display face, and Display composes both with a gasket. --depth: 0 removes the gasket’s physical spacing. Texture is a decorative CSS image, separate from daisyUI’s noise. Never use it to carry information the reader needs.

embed and embed-dark are for a widget framed inside someone else’s page. The host picks one (for example by prefers-color-scheme) and applies embedThemeProperties({ accent, fontFamily }) to the themed element with style.setProperty. The owner’s #rrggbb accent drives daisyUI primary and accent. readableContentColor picks black or white content by WCAG luminance, so primary text always reaches 4.5:1. A supplied font-family list comes before system-ui. Without one the themes use system-ui. Your host parses the accent and font list before passing them in. EMBED_THEME_TOKENS repeats the default chrome colours for surfaces outside daisyUI, such as a launcher in a shadow root.

--stipe-art-{coral,blue,mint,grape,yellow,cream,ink,inkcap,graphite} are illustration roles, separate from control-state colours. Each has explicit light and dark defaults through light-dark() and resets at every theme boundary. Themes can override these roles to retint artwork. Drawings on the page use --stipe-sketch-ink. Figure eye cut-outs use --color-base-100 directly.

You can nest data-theme inside another theme. A complete palette declares its own typography and muted ink at its theme boundary, so nesting latte inside pirate-radio restores latte typography. A values-only finish inherits colours, color-scheme, fonts, button radius and muted ink. It sets --root-bg: transparent so daisyUI does not paint a new page behind it, and declares only the material values it changes.

CSS inherits through the DOM, not through React portals. Dialogs, dropdowns and popup comboboxes in Caps take a portal container option. Dialogs and dropdowns also use the host’s OverlayRoot destinations when supplied; without one they fall back to the document body. Popup comboboxes use their explicit container or the document body. For a nested theme, choose an unclipped destination inside that theme. An ancestor with clipping, transforms or containment can affect positioning, so choose the portal container as part of your overlay layout.

readout and readout-night are independent light and dark display palettes. They use Instrument Sans prose, Maple Mono chrome and code, faint one-pixel region and row rules, and zero raised/inset shadows. They extend the same Stipe material hooks; Caps geometry and interaction stay unchanged. Load Stipe’s full fonts.css for both families. Code consumers disable ligatures in their code presentation. terminalPalette() supplies the corresponding semantic syntax colors so a docs adapter does not maintain a second palette.

pirate-radio is a complete, nestable cream-paper palette with orange-red spot colour, mono labels, tight rules and a procedural print texture. pirate-radio-night reverses it to warm ink-black paper. It uses Instrument Sans for body text, Anton for headings and Departure Mono for every mono role, including labels and numerals. No external font request or raster texture is needed.

The theme sets generic Caps hooks instead of adding component props: --caps-heading-font, --caps-heading-weight, --caps-heading-case, --caps-heading-tracking (default -0.025em, 0.02em here), --caps-heading-line-height (1.2 here), --caps-label-font (mono here), --caps-card-image, --caps-window-image and --caps-print-texture-size. Each keeps the previous appearance when unset. --font-numeric defaults to var(--font-mono) at every theme boundary, and Caps’ numeric readouts use it with tabular numerals. --stipe-font-synthesis keeps browser defaults in other themes and is none here, so the regular-only fonts never get synthetic weights or italics. CSS motion uses short 60/90/140/200 ms press timings without overshoot. All of these reset at nested theme boundaries.

Stipe supplies --stipe-layer-desktop (0), --stipe-layer-windows (10), --stipe-layer-chrome (20), --stipe-layer-popover (30), --stipe-layer-dialog (40), --stipe-layer-toast (50), and --stipe-layer-tooltip (60). Maosaico owns the shell roots and consumes this scale. Components select a named portal layer; window focus ranks remain local to the windows root. The scale does not cross an iframe boundary.

The --stipe-overlay-shadow-outset token is a small CSS-pixel allowance (4px) around measured overlay content. The isolated dashboard includes it in the regions the host cuts out of sibling App frames, preserving a little shadow.

Each theme boundary resets motion so nested themes do not inherit an outer spring. Durations (squish, fast, normal, slow, spring-duration, stagger) use CSS milliseconds or seconds. --stipe-motion-bounce (0–1), --stipe-motion-stretch and --stipe-motion-steps are unitless. Distance, blur, shake and fuse use pixels. Press travel is --stipe-press-depth in pixels and --stipe-press-scale is a unitless scale. Enter/exit easing uses --stipe-motion-ease / --stipe-motion-ease-exit.

Caps’ @fungi.computer/caps/lib/motion exports readMotion(element) for event handlers and useMotion(ref) for live React controls. Both read the element’s computed tokens, including nested overrides. The hook observes theme/class/style changes on its element and ancestors plus the OS motion preference; use a stable container ref, not a per-frame animated child. The reader returns milliseconds and pixels; reduced motion has zero travel, bounce, stretch, blur and shake, and 120ms durations for opacity fades. Consumers must select opacity-only animation when reduced is true. Caps CSS durations and animated controls use these same tokens. Under reduced motion, CSS transitions are restricted to short opacity fades, spatial keyframes are suppressed, and list FLIP skips movement even though fade durations remain nonzero. Theme feel values still require visual sign-off.