The Quaso design system
Use the shared design system to build controls and layouts that fit the Quaso app and project website. The component catalog shows working examples and their states.
After development setup, run npm run dev and open
the catalog. The catalog needs no account or project data and
makes no API calls. npm run build:web also includes design.html in the app build.
The app, catalog, and public website depend on the workspace package @quaso/design-system in
packages/design-system. It owns the base React components, shared CSS, theme tokens, icons, mascot,
and licensed fonts. Editing the package updates all three sites. The website's static build
bundles the package's styles and referenced assets without adding client-side JavaScript.
The package has no dependency on the application, API types, router, or translation model.
Application workflows and route adapters stay in packages/web; landing-page and documentation
layouts stay in site. Import through the package's exports, not relative paths into its source.
ESLint and the package's dependency test enforce that boundary.
Use shared controls
Import components from @quaso/design-system, and load the shared stylesheet once before your
site's layout styles:
import { Button, Field, H1, Input } from "@quaso/design-system";
import "@quaso/design-system/styles.css";
@quaso/design-system/icons, /tokens, and /contrast expose the icon collection, palette data,
and color calculations. @quaso/design-system/assets/* exposes the mascot, patterns, fonts, and
font licenses for builds that copy assets.
Use Button, Input, Select, Checkbox, Radio, Switch, TextArea,
Label, Fieldset, Progress, Table, Details, Summary, and Dialog. Field adds an associated
label, hint, and validation error to an input. Native props, event handlers, and refs still work.
The app keeps its router-aware Link and ButtonLink adapters locally; both compose package
base components. Use the package's A and AnchorButton for browser navigation, external destinations,
and downloads. Button defaults to type="button"; forms submit explicitly
with type="submit". Busy buttons keep keyboard focus and ignore repeated clicks.
The package also provides tabs, dropdowns, notices, empty states, progress, chips, status markers, and icons. File trees, toasts, translation status, placeholders, and the working translation composition in the catalog demonstrate application components built with these base components. Business workflows remain in the app; their controls and colors come from the system.
The app's MainNavigation groups workspace links under Project, Contribute, and Manage. Desktop
pages use a sidebar; narrow screens and the focused editor use a compact menu. Settings has its
own grouped sidebar and a matching mobile selector. These layouts stay in the application. Dropdown also powers the account menu: it closes on Escape,
outside clicks, or when focus leaves, and keeps ordinary links and actions in the Tab order.
Current links carry aria-current="page"; keep the active state and keyboard focus distinct.
The header's ThemeSwitch variant="menu" keeps system, light, and dark choices in one icon button;
the catalog uses variant="switch" for its direct light/dark toggle.
ESLint rejects native control tags in the app, site, and design system outside the shared
component implementations. A repeated control belongs
in this library and the catalog. Layout elements such as section, div, span, and p stay native.
Color has a single source
packages/design-system/src/styles/tokens.ts defines named color groups and shades. The light and dark
maps assign those shades to named roles. Component styles use roles such as var(--surface),
var(--fg), var(--danger), and var(--selected) instead of literal colors. Brand examples can use
palette variables such as var(--lime-400) directly. Transparency mixes declared palette
colors with transparent.
The brand palette pairs deep plum (#261230), lime (#d7ff64), mint (#80e5dc), and
off-white (#f4f4f1), inspired by Astral. Light mode uses pale solid
surfaces and plum text; dark mode uses plum surfaces and light text. Lime identifies primary
actions in both. Status colors have separate named roles. Keep their shapes and labels so readers do not need to
distinguish colors to understand a state.
After editing the palette or theme roles, run:
npm run design:tokens
npx vitest run packages/design-system
The generator updates theme.css and both favicons. Commit the generated files with the palette.
The tests check generated output and listed text/control contrast pairs in both themes. They also
simulate two types of red–green color blindness to check the state colors. These checks do not
replace a review of the rendered pages. Authored stylesheets must not
introduce literal colors outside the palette.
Themes follow the system until someone chooses light or dark. The catalog switch and the app's
three-way theme menu use the package's theme store, persisted as quaso.theme in browser local
storage. The public website follows the system preference with CSS and works without JavaScript.
Typography and comfort
Use H1 through H4 for titles. All four Jersey fonts ship locally at their native 400 weight;
do not stretch, compress, or synthesize bold. The same mapping applies to documentation headings.
| Level | Font | Default size |
|---|---|---|
| H1 | Jersey 25 | 40–56px, responsive |
| H2 | Jersey 20 | 28–36px, responsive |
| H3 | Jersey 15 | 28px |
| H4 | Jersey 10 | 24px |
Use display only for brand and marketing titles. ui keeps workspace headings compact:
32px for H1, 24px for lower-level headings. Repeated records and sidebars also use the compact
size; dialogs use 28px. These sizes come from the shared heading tokens in foundations.css.
Long titles wrap, including on narrow screens and with enlarged text.
Body copy, labels, input values, source strings, and translated strings use the system font. Keep
natural letter spacing, use lang and dir when the language is known, and let translation text
wrap with a 1.65 line height. Do not use Jersey for strings people review.
Spacing, font sizes, and motion durations live in styles/foundations.css. Status
colors always have a shape and label. Selected rows use a background and stronger text weight;
keyboard focus retains its visible outline. Shared styles include reduced-motion rules. Check the affected workflow with reduced motion
enabled; accessibility review describes the broader checks.
Pixels and shapes
Corners are square throughout the interface. Icons use straight segments; status markers are empty, half-filled, and checked squares. Shadows have a solid offset without blur. Switches and choice controls keep native keyboard behavior inside square outlines.
Wordmark combines the Quaso cat with Jersey 25. QuasoMascot displays the same mark on its own.
The source is packages/design-system/src/assets/quaso-cat.svg, drawn on a 32×26 grid with nine colors.
It uses only pixel-aligned paths and shape-rendering="crispEdges". Prefer the SVG at any
sizes; the 32×26 PNG and 768×624 nearest-neighbor PNG ship alongside it. Never smooth the sprite.
PixelPattern provides lime, mint, and plum variants of a repeating pixel weave. Use it in
brand banners and empty decorative areas, with stepped shapes if needed. Keep patterns
away from translation text, forms, tables, and other reading surfaces. Patterns are decorative
and hidden from assistive technology.
Controls share --control-height: 44px at the default font size, growing with larger text.
Small buttons reduce padding and type size without reducing the touch target. Wrap checkboxes,
radios, and switches in Label so the full row is a touch target, at least 44px high at the default text size. Multiline fields may grow
taller; single-line selects and inputs start at the same height on every device.
Check a change
Check the affected examples in light and dark mode, then the real workflow that uses them. Check narrow screens, keyboard focus, and long or right-to-left text when relevant. Targeted checks are:
npm run lint:react
npm run typecheck
npx vitest run packages/design-system
npx vitest run e2e/catalog.test.ts e2e/design_system.test.ts
npm run site:build
The Jersey fonts are distributed under the SIL Open Font License. Each font's license ships in
packages/design-system/src/assets/Jersey-*-OFL.txt. Both app and site builds copy these licenses
alongside the bundled fonts.