Start a project
Every new app, site, extension or artifact starts the same way. Do these steps in order and the frontend foundation is done before the first screen.
1. Pick the theme and mode
- Use the default for the project type from the theme table (Graphite for B2B, Ember for fitness, Paper for learning, and so on).
- Decide the mode policy: home mode only, or a light/dark toggle that defaults to the system setting. Most apps only need home mode.
- Write the decision at the top of the project's own README or AGENTS.md:
UI: Enma · ember · dark (home), toggle: no. Every later screen, by you or by an agent, follows that line.
2. Bring Enma into the project
tokens.json in @enma/tokens is the source of truth. Everything a project needs is generated from it and published as packages, never typed by hand. After the one-time setup:
pnpm add enma@npm:enma-design
| What the project uses | Comes from | Used by |
|---|---|---|
enma/css | tokens, theme maps and component styles in one file | Web, artifacts, extensions |
enma/fonts/<theme> | self-hosted woff2 for one theme | Web, extensions |
enma/react | the components (index.d.ts is the API contract) | React and Next.js |
enma/native | provider, hook and components with native theme objects | React Native |
enma/tokens | typed values, theme info, native themes, Figma variables | Scripts, Tailwind, tooling |
No starter or CLI is required, but a starter gives you all of this wired up.
Without packages
For a one-off prototype that can't install anything, copy enma.css from @enma/css (it already includes the tokens) and the font file for the theme. The build script in packages/tokens/scripts/build.mjs shows how every output is generated from tokens.json, if a project ever needs its own format.
3. Load only the fonts of your theme
The docs site loads every theme's fonts so the gallery works. A real project loads only its own, as woff2 with font-display: swap. One import does it:
import 'enma/fonts/ember';
| Theme | Fonts it loads |
|---|---|
| Graphite | Inter Tight 400/500/600, JetBrains Mono 400/500 |
| Glass | Hanken Grotesk 400/500/600/700 |
| Paper | Instrument Serif 400 and italic, Instrument Sans 400/500/600 |
| Mineral | Sora 600, Manrope 400/500/600 |
| Enamel | Space Grotesk 400/500/600/700 |
| Ember | Geist 400/500/600 |
4. Build screens from the system, in this order
- Layout and navigation (
TabBaron mobile, a sidebar or top bar on web). - The hero of each screen (
Card variant="hero"with aStat, or a display title). - Content with existing components (
ListGroup,Table,Card,Stat,Bars,LineChart). - States: loading, empty, error and offline for every screen (see Engineering standards).
- Only then write custom UI, styled with
--e-*variables or the Tailwind mapping, never raw values.
When a component is missing (dialog, menu, popover, combobox, date picker), build it on an accessible headless primitive (Radix UI or React Aria on web) and style it with Enma tokens. If it will be reused, add it back to Enma so the next project gets it.
5. Customize inside the tweak budget
A project may change the accent, the display font and the radius dial. Put the overrides in one file loaded after enma/css. The selectors mirror the theme map, so each override wins wherever the theme applies (on the root, inside a nested mode switch, and on an element that sets both). The playground writes this file for you.
/* overrides.css: Alter uses Ember with a hotter accent and tighter controls */
[data-enma='ember'],
[data-enma='ember'][data-theme],
[data-enma='ember'] [data-theme]:not([data-enma]) {
--e-r-control: 14px;
}
[data-enma='ember'][data-theme='dark'],
[data-enma='ember'] [data-theme='dark']:not([data-enma]),
[data-theme='dark'] [data-enma='ember']:not([data-theme]) {
--e-accent: #ff6a2c;
--e-primary: #ff6a2c;
--e-accent-ink: #ff9a66;
}
Re-check contrast for any color you change (text pairs 4.5:1, marks 3:1). Anything beyond these three dials becomes a new theme in Enma, not a project hack.
6. Ship with the checklist
Run the definition of done before a screen counts as finished.