Interface design
This page lists the design rules for Otto's web and desktop interface, and how to check a UI change before you open a pull request.
Otto follows the n8n brand system: neutral gray surfaces, pink primary actions and clear text. Otto keeps its own symbol. It's a product mark, not a copy of the n8n logo.
Keep it simple
Section titled “Keep it simple”Chat is the main work area. Tasks, schedules, apps, channels and the Vault support it. Keep navigation and results clear, and keep decoration out of active work.
| Do | Don't |
|---|---|
| Let the content speak for itself | Add eyebrow labels above titles |
| Use a title only when it adds something | Write titles that restate what the screen already shows |
| Keep text short, with little text per screen | Explain a control that's already clear |
| Show a state with an icon or text, plus its color | Let color alone carry a state or a safety decision |
| Keep statuses, tabs and navigation items clear controls | Style controls as marketing capsules |
The palette lives in src/web/theme.css. Page styles use its variables. Don't add page-specific colors.
| Role | Variable | Light | Dark |
|---|---|---|---|
| Canvas | --background |
--white (#fafafa) |
--black (#191919) |
| Sidebar | --sidebar |
#f5f5f5 | #1f1f1f |
| Raised surface | --surface |
--gray-0 (#fff) |
--gray-90 (#242424) |
| Inset surface | --surface-inset |
--gray-20 (#eaeaea) |
--gray-85 (#353535) |
| Main text | --foreground |
--black |
--white |
| Secondary text | --text-secondary |
--gray-80 (#464646) |
--gray-30 (#d6d6d6) |
| Muted text | --muted-foreground |
--gray-70 (#676767) |
--gray-40 (#bfbfbf) |
| Primary action | --primary |
--brand-pink (#ff91ac) |
--brand-pink |
| Text on primary | --primary-foreground |
--black |
--black |
| Selected surface | --accent-soft |
--pink-10 (#ffe8ec) |
--pink-80 (#5a0026) |
| Link and focus | --link, --ring |
--pink-70 (#87003c) |
--brand-pink |
| Sent message | --chat-sent |
--pink-20 (#ffd0d9) |
--pink-80 |
| Text on sent message | --chat-sent-foreground |
--black |
--white |
Use black text on brand pink. Brand pink text doesn't have enough contrast on a light surface, so light-theme links and keyboard focus use deep pink, --pink-70.
| Element | Minimum contrast |
|---|---|
| Body text | 7:1 |
| Small text, placeholders, links and status text | 4.5:1 |
| Essential control boundaries and focus | 3:1 |
| Dividers | Can be softer |
Success (--success), warning (--warning), error (--error) and information (--info) are product states, each with a -soft background. Always pair them with an icon or text.
The interface uses n8n Baumer Uniwidth, and code uses n8n Baumer Mono. Both are served locally from public/fonts. Use regular weight for body text and medium for headings.
| Use | Size | Line height |
|---|---|---|
| Page title | 32 px; 28 px on phones | 36 px; 32 px on phones |
| Sign-in story title | 40 px | 42 px |
| Message and long text | 16 px | 1.6 |
| Control and supporting text | 14 px | At least 18 px |
| Small graphic detail | 10 px | At least 14 px |
Turn on standard ligatures, balance short headings, and let paragraphs wrap naturally. Keep code, passwords, URLs and user content in their original case. Don't apply the brand's uppercase capsule style to technical data.
Spacing and shape
Section titled “Spacing and shape”| Rule | Value |
|---|---|
| Layout spacing | 8 px steps; 4 px and 12 px for small controls and details |
| Corners | 8 px for controls, 12 px for content, 16 px for dialogs; fully round for dots, avatars and switches |
| Content width | Pages up to 1184 px, chat up to 880 px |
| Phone inset | 16 px |
| Depth | Borders and space for lists; shadows for menus, dialogs and the composer |
Page padding changes with the available width. Pages share one title size, and actions align with the page heading.
Icons and brand details
Section titled “Icons and brand details”| Item | Rule |
|---|---|
| Icon set | Lucide, with a 2 px stroke in a 24 px view box |
| Icon sizes | 16 px inline and for status, 24 px for navigation and page titles, 32 or 48 px for large empty states |
| Icon color | Neutral by default, pink for selected navigation, state colors on status icons |
| Connected services | Logos keep their official colors |
| Dot grid | 24 px, only on the sign-in story and the empty conversation. Never in messages, tables, forms, app lists or Settings. |
Don't show a construction grid. Keep the existing reduced-motion behaviour. The brand guide doesn't define motion for product interfaces yet.
Rules for each surface
| Surface | Rule |
|---|---|
| Shell | Neutral sidebar, pink selection, quiet utility controls |
| Chat | Neutral received bubbles, pink sent bubbles, readable tables and files |
| Composer | Clear boundary, visible focus, one primary send action |
| Tasks and schedules | Shared title, tabs, row spacing and action height |
| Apps and channels | Neutral cards, visible connection state, consistent controls |
| Vault | Clear selection, fields and error states; secure forms preserved |
| Trust | One summary line, one cost card, a quiet statement list |
| Sign-in | Plain capability text, pink sign-in action, one inset story panel |
| Onboarding | Clear connection steps, no decorative grid behind fields |
| Credentials and approvals | Clear action hierarchy; origin and expiry preserved |
| File previews | Clear captions and toolbars; local scrolling preserved |
| Native desktop setup | Same palette and type scale as the web app |
Check your change in the running app
Section titled “Check your change in the running app”The test suite doesn't cover how the interface looks. Run Otto with synthetic data and look at the change yourself.
flowchart TD change([Your UI change]):::accent --> data[Load synthetic data] data --> sizes[Light and dark,<br/>desktop and phone] sizes --> states[Menus, focus, errors,<br/>empty and full] states --> checks[Run the checks] checks --> report[Report UI and live<br/>checks separately]:::go
- Check light and dark themes at desktop and phone sizes.
- Open menus and dialogs. Check selected tabs, keyboard focus, disabled controls, long text, tables and errors.
- Check empty and populated states.
- Confirm the fonts load, text contrast holds and the page doesn't scroll sideways.
- Run
pnpm run check,pnpm run test:integrationandpnpm run build. Add screenshots with synthetic data to your pull request.