Skip to content

Repository files navigation

FiestaUI

FiestaUI is the FiestaBoard design system: React components built on Base UI with Tailwind v4 design tokens, published as @fiestaboard/ui.

Component catalog: the Storybook is deployed to GitHub Pages on every push to main.

Installation

npm install @fiestaboard/ui

No token, no .npmrc, no registry configuration — the package is public on registry.npmjs.org and installs anonymously, in CI and Docker builds as much as locally.

Before 1.6.3 this package lived on the GitHub Packages npm registry, which requires authentication for every read even when the package is public. If you still have a @fiestaboard:registry=https://npm.pkg.github.com line in an .npmrc anywhere, delete it — it will pin you to the old registry and to versions no longer published there.

Peer dependencies: react / react-dom ^19, lucide-react, and tailwindcss ^4 in the consuming app.

Styling contract

FiestaUI ships no compiled utility CSS. The consuming app runs Tailwind v4 and generates utilities for the class strings inside this package. In your Tailwind entry CSS:

@import "tailwindcss";
@import "@fiestaboard/ui/fonts.css";
@import "@fiestaboard/ui/theme.css";
@source "../node_modules/@fiestaboard/ui/dist";
  • theme.css carries the design tokens (@theme inline, :root / .dark custom properties), the base layer, the component animation keyframes, and the dark custom variant.
  • fonts.css is the opt-in Geist font registration (@fontsource-variable/geist + @fontsource-variable/geist-mono @font-face rules, ~52 KB fetched for a Latin-only page). Import it alongside theme.css unless your app supplies Geist itself — e.g. via next/font, a CDN, or a self-hosted subset. If you self-host, skip fonts.css and register faces named "Geist Variable" / "Geist Mono Variable" (the names theme.css's --font-geist-sans / --font-geist-mono tokens reference); without either, the tokens degrade gracefully to the system font stack.
  • The @source line is mandatory — Tailwind v4 does not scan node_modules by default, and without it component styles silently vanish. Adjust the relative path to wherever your CSS file lives.
  • Dark mode is class-based: toggle the dark class on <html>. FiestaUI only defines the variant; your app owns the toggle.

Seasonal theming

theme.css inlines only the live season (Pride) — it activates automatically when the app shell stamps .pride-month on <html>. The other seasons are design drafts and are shipped as opt-in stylesheets rather than bundled into every consumer, so you only pay for the ones you use:

@import "@fiestaboard/ui/seasons/christmas.css";

Import the file for a season only once you've promoted it (moved its entry into SEASONS and taught the app shell to stamp its htmlClass). All drafts are previewable in Storybook's Season toolbar without any consumer setup.

Migration (breaking): the draft season data (DRAFT_SEASONS, ALL_SEASONS) is no longer exported from "@fiestaboard/ui" — it's Storybook-only data and now tree-shakes out of production bundles. If you previewed drafts, import them from the subpath instead: import { ALL_SEASONS } from "@fiestaboard/ui/lib/seasons-drafts". The live-season API (SEASONS, getActiveSeason, useActiveSeason, …) is unchanged.

Usage

import { Button, Card, CardContent, cn } from "@fiestaboard/ui";

export function Example() {
  return (
    <Card>
      <CardContent className={cn("flex gap-2 p-4")}>
        <Button variant="brand">Save</Button>
        <Button variant="outline">Cancel</Button>
      </CardContent>
    </Card>
  );
}

Development

npm install
npm run storybook        # component workbench on :6006
npm run build            # dist/ — ESM + d.ts + theme.css
npm run lint && npm run typecheck && npm run format:check
npm run build-storybook && npm run test-storybook   # axe a11y sweep (needs the static build served on :6006)

Visual parity rule

This package was extracted from FiestaBoard's web/src/components/ui with a pixel-parity guarantee. Token values in src/styles/theme.css and component class strings are contract, not implementation detail — changes to them are visible in every consumer. Change intentionally and version accordingly (patch: fixes, minor: additive components/variants, major: breaking API or visual changes).

Testing against a local consumer

Don't point FiestaBoard's Docker build at an unpublished version. For local iteration:

npm run build && npm pack   # produces fiestaboard-ui-<version>.tgz
# in the consumer: npm install /path/to/fiestaboard-ui-<version>.tgz

Releasing

Releases are continuous: every merge to main that changes shipped code bumps the version, tags v<version>, publishes to registry.npmjs.org, and creates a GitHub Release. scripts/release/gate.mjs decides whether a merge earns a release and how big a bump; CI-only changes (workflows, scripts/ci, VRT baselines) mint nothing. Actions → Release → Run workflow remains the manual override.

Publishing uses npm Trusted Publishing (OIDC). The workflow's id-token: write permission mints a short-lived credential that npm exchanges for publish rights, so there is no npm token anywhere — nothing to store, leak, or rotate. Provenance is attested from the OIDC claims, linking each tarball to the workflow run that built it.

This requires a trusted publisher configured on the npmjs package — repository Fiestaboard/FiestaUI, workflow release.yml. Without it the publish step fails outright; it does not fall back to a token.

Publishing by hand

npm run release:npmjs builds and publishes from a local checkout. It exists as an escape hatch for when the workflow is broken — normal releases should go through CI.

npm login                            # once — web-based, completes 2FA in the browser
npm run release:npmjs -- --dry-run   # inspect the tarball contents first
npm run release:npmjs                # publish; completes 2FA interactively

The account's 2FA mode is auth-and-writes (check with npm profile get), so the publish needs a second factor. Do not pass --otp unless you have a TOTP authenticator app — with a passkey or hardware security key there is no code to type. With auth-type=web (npm's default) the CLI prints a https://www.npmjs.com/login/<uuid> URL; open it, satisfy the passkey, and the publish continues.

The script rebuilds first because files ships only dist and there is no prepack hook, so a stale dist/ would otherwise publish silently-wrong contents — unrecoverable, since npm forbids republishing a version. Publish from an up-to-date main: the version comes from package.json, so a stale checkout would point npmjs's latest tag at a superseded release.

Downstream upgrade automation

Every release triggers .github/workflows/downstream-upgrade.yml, which keeps a single evergreen PR open on Fiestaboard/FiestaBoard (branch fiestaui-upgrade) pinning @fiestaboard/ui to the newest version. If the bump breaks FiestaBoard, Claude (Opus) TDD-fixes it (max 3 attempts) and FiestaBoard CI must pass before the PR is labeled upgrade-green; otherwise it's labeled upgrade-blocked and the maintainer is pinged. A human on FiestaBoard always does the merge.

Manual run / backfill: Actions → Downstream Upgrade → Run workflow (optionally set version; dry_run computes the bump without pushing).

Required repo secrets: CLAUDE_BOT_APP_ID, CLAUDE_BOT_APP_PRIVATE_KEY (GitHub App with write access to FiestaBoard), CLAUDE_CODE_OAUTH_TOKEN. Design: docs/superpowers/specs/2026-08-01-downstream-upgrade-design.md.

License

MIT

About

FiestaUI — the FiestaBoard design system. React components on Base UI + Tailwind v4 tokens, published as @fiestaboard/ui.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages