@caqophony/ui
Generic React UI components built on Gossamer semantic design tokens (Tailwind). Maintained centrally by Caqophony and MIT licensed.
This library is optional. Apps generated by Caqophony may use it so that common UI (buttons, cards, dialogs, tables, page layout) is not regenerated from scratch for every app, which saves tokens. It is a courtesy, not an SDK: nothing in a generated app requires it. If reuse is turned off for a project, the app is built without this dependency and its components are written from scratch. It contains only generic UI: no platform, product, auth or data-access code.
Install
pnpm add @caqophony/ui # or npm i / yarn add
Peer dependencies: react and react-dom (^18 || ^19). @caqophony/ui/charts
requires the optional peers chart.js and react-chartjs-2: install them
yourself if you import that subpath. No next dependency.
The package is ESM-only. Subpath types (@caqophony/ui/charts) resolve through
the exports map, so TypeScript needs moduleResolution of bundler (or
node16/nodenext).
DiceBearAvatar renders an image from api.dicebear.com at runtime, so it
makes a network request from the user's browser; do not use it where that is
unacceptable.
Styles (Tailwind v4)
Components use Gossamer token classes (bg-primary, text-muted-foreground,
border-intent-error-border, ...). Tailwind v4 does not scan node_modules
and has no mapping for these names, so import the stylesheet this package
ships, after Tailwind, in your global CSS:
@import 'tailwindcss';
@import '@caqophony/ui/styles.css';
styles.css defines the token variables (light, and dark under
@media (prefers-color-scheme: dark), the same OS setting Tailwind v4's dark:
variant follows), maps them to
Tailwind v4 colour/radius/z-index/opacity/duration utilities and registers the
package's built files as a Tailwind @source. Override any --token after the
import to re-theme.
Styles (Tailwind v3)
Inside the Caqophony monorepo, an app takes the Gossamer preset and adds the
library to content. Do not map the tokens by hand in theme.extend.colors:
that left bg-primary-solid with no rule, or with a rule reading a variable
nothing declares, and neither one raises an error.
import { gossamerPreset } from '@caqophony/tokens/tailwind-preset';
export default {
presets: [gossamerPreset],
darkMode: ['class'],
content: ['./app/**/*.{ts,tsx}', '../../packages/ui/src/**/*.{ts,tsx}'],
};
The preset maps the library's tokens and declares its variables (light, and
dark under .dark), and adds the utilities and the not-first: variant a v3
theme cannot express. It only adds names: stock Tailwind keeps its values, your
theme wins over the preset, and a variable your CSS declares wins over the
preset's.
pnpm exec tsx scripts/check-ui-classes-compile.ts covers the two surfaces that
draw the library: the monorepo's product app on Tailwind v3, and a generated
app's globals.css plus styles.css on Tailwind v4, each against the classes
that surface can reach. It fails on a class Tailwind generates no rule for, or a
rule reading a variable that does not reach the element by default. It runs in
pre-push.
The preset is not published. Outside the monorepo, a Tailwind v3 app needs
./node_modules/@caqophony/ui/dist/**/*.js in content and the same mapping,
variables, utilities and variant in its own config, or Tailwind v4 and
styles.css above.
Animation and prose
On Tailwind v4, styles.css defines the animate-in / animate-out utilities
the modal and tooltip use, and loads @tailwindcss/typography for
MarkdownRenderer, which @caqophony/ui depends on. There is nothing to add.
On Tailwind v3, install tailwindcss-animate and @tailwindcss/typography as
plugins.
Use
import { Button, Card, PageContent } from '@caqophony/ui';
import { BarChart } from '@caqophony/ui/charts';
import { MarkdownRenderer } from '@caqophony/ui/markdown';
import { MarketingCta, MarketingFooter } from '@caqophony/ui/marketing';
import { Band, Display, Stack, Text } from '@caqophony/ui/material';
@caqophony/ui/material holds what a designed page is composed from: bands,
display type, layout, and flat illustration pieces. Each colour is a token, so a
theme change reaches all of it. Set a Band's surface to night and every
library component inside reads on the dark ground, with no prop of its own.
@caqophony/ui/material-tones holds FILL and STROKE, the classes art draws
in. It and @caqophony/ui/material-art are the entries with no 'use client'
directive, so a server component can use them. Every other entry point carries
the directive.
An app's own art is data, not code: a closed tree of SVG elements in tones, in a
*.art.json file in art/ beside app/, outside the library. Art from
@caqophony/ui/material-art draws it, as a Figure's one child, and nothing else
makes elements in the slot. Shade paper with a held step at low opacity: paper
keeps its colour in dark mode and mist-* does not.
Licence
MIT, see LICENSE.