Overview
Upmo runs always-on AI agents that carry out tasks for you. They run on Upmo’s managed cloud at upmo.ai, or on infrastructure you own with the open-source release from upmo.dev. The interface should feel the way the agents work: calm and steady, and always honest about what is happening. This book holds the rules that give every Upmo screen that feel. It is public, so people outside Upmo can build on it too. Every component, page and marketing surface follows it. Every value it names lives in tokens.json. Tokens are named design values, such as colours and spacing.
The base library is Mantine 9, an open-source React component library. Upmo components are Mantine components styled with these tokens. We extend Mantine only where Upmo needs something it does not have: the caret and task status. The Mantine section shows how the theme object maps. Icons come from Tabler Icons, the set Mantine is built to work with.
Content
Section titled “Content”- Voice. Write plainly and directly, to “you”. Keep sentences short. “Your data. Your control.” is the model. Say what happens: the button reads “Start task”, and the toast reads “Task started”.
- Name. In prose, write
Upmowith a capital U and no caret. The domains are lowercase.upmo.aiis the product and the managed service.upmo.devis the open-source, self-hosted release. The wordmark is the SVG inassets/logos. Never type it. - Tagline. “Always up.” has a period and no hyphen. “Always-on” takes a hyphen only before a noun: “always-on agents”, but “the agents are always on”.
- Casing. Use sentence case everywhere: titles, buttons, labels, menu items, tabs. In sentence case, only the first word and names start with a capital. Use uppercase only in
overlinegroup headers. - Numbers. Use tabular figures (
font-variant-numeric: tabular-nums) wherever digits line up. Tabular figures give every digit the same width. Write durations as “4.2 s”. For tokens in and out, put the caret-up glyph before the input count and the caret-down glyph before the output count: “^12k ˅1.2k tok”, exact on hover. Write money with the currency symbol: “€0.011”. No directional arrows anywhere. - Errors. Say what went wrong and what to do next: “Provider key rejected. Check the key in Settings and try again.” No apologies, no “Oops”. When a task fails, the person needs the next step, not an apology.
- Status words. Every status shows a word or an icon as well as its colour: “Working”, “Needs approval”, “Done”, “Failed”.
- Punctuation. No em dashes anywhere. Use a comma, a colon or a new sentence. No emoji in UI text.
- Familiar words. Use the words people already know from mainstream apps: task, chat, run, usage, notifications, folder. One concept has one word everywhere, in the interface, the docs, the API and the code. The Glossary lists them.
- Agents and tasks. A task is work an agent does for you. Its status moves through Preparing, Planning, Working and Checking, then Done or Failed. Use those words. Never call them “stages” or “phases” in the interface. A step is a stepper step in a form, or a step in a task’s plan.
Colour
Section titled “Colour”There are two themes, and light comes first. Light sits on Bone (surface-000). Dark sits on Ink. Each token holds both values. Components read tokens only, never a literal colour value.
- Grounds.
surface-000is the page.surface-100is anything raised: cards, inputs, modals, menus.surface-200is anything sunken: sidebar, code, table headers. Usesurface-hoverfor hover andsurface-selectedfor the selected item. - Text. Use
inkfor headings and labels,ink-secondaryfor prose andink-mutedfor metadata and placeholders. Useink-disabledonly on disabled controls. Onsurface-inverse, useink-inverse. - The green.
emeraldis the brand colour. Use it for the caret, the “up”, status icons and text at 24px and above. It is only 3.9:1 on Bone, so green text at body size and links useemerald-deep. Primary buttons fill withemerald-deepand useink-on-brandtext. Hover isemerald-hover. Pressed isemerald-active.emerald-softis the tinted ground for light buttons, success alerts and the done badge. - Borders.
borderis decorative: cards, dividers, table rules.border-strongis any edge that defines a control: inputs, checkboxes, radios, outline buttons. The difference is contrast. Control edges meet 3:1. Decoration does not have to. - Status.
success(green),warning(amber),danger(clay red) andinfo(blue) each have a-softground. Success and danger have almost the same lightness. So each status also carries its word or icon: the done caret, the warning clock, the failed X, the info circle. Never a lone coloured dot. - Focus. Every focusable element gets the
focusring: 2px solid, 2px offset, in both themes. Nothing else draws a ring. - Dark theme. Dark is not the light theme inverted. Greens get lighter:
emerald-deepbecomes a 6.5:1 mint on Ink and takes Ink text. Surfaces get lighter as they rise. Shadows get deeper. Setdata-theme="dark"on the root. Mantine readsdata-mantine-color-scheme="dark".
Typography
Section titled “Typography”- UI face: Manrope (
ui), weights 400 to 700. Load it from Google Fonts or bundle it as a variable font.body(15/22) is the default. Usebody-smfor metadata,labelfor form labels and small buttons,captionfor hints andoverlinefor uppercase group headers. Headings run fromh1toh4, with oneh1per page. - Display face: Familjen Grotesk (
display), weight 700 only. It is for the wordmark (shipped as SVG, never typed) and for the marketing stylesheroandhero-sm. It never appears inside the app. - Code face: Fira Code (
mono), incodeandcode-sm. Use it for code blocks, tool call arguments, versions and keyboard shortcuts. Ligatures are on. - Measure. Measure is the length of a line of text. Running text stays at or under
measure(68ch). Chat replies, descriptions and docs wrap there. - Weights. 400 for text. 500 for captions. 600 for labels, h2 to h4 and button text. 700 for h1 and display. Never 300 or 800.
- Minimum size. 12px (
caption,code-sm). Nothing smaller.
Spacing and layout
Section titled “Spacing and layout”- Scale. Spacing runs from
space-1(4px) tospace-16(64px). Inside a component, usespace-2andspace-3. Cards padspace-4on phones andspace-5on desktop. Sections sitspace-8apart. - App shell. The shell is the frame around every page. From
bp-md, the sidebar issidebar(248px) onsurface-200. Frombp-sm, it is a 56px icon rail. Belowbp-sm, it is a drawer. The top bar istopbar(48px). Content padsspace-4on phones,space-6on tablets andspace-8on desktop. It stops growing atbp-xl. - Grids. Cards use
auto-fill, minmax(320px, 1fr)withspace-5gaps, so the number of columns follows the width. Form rows go two across frombp-xs. - Side gutter. Never less than
space-4. The page never scrolls sideways. Wide tables and code scroll inside their own container. - Alignment. Use flex and grid with
gap, not margins. Repeated items share edges, baselines and inner padding.
Radius, borders, shadows
Section titled “Radius, borders, shadows”- Radius.
radius-md(8px) on controls.radius-lg(12px) on containers.radius-xl(16px) on overlays and the composer.radius-fullon badges, avatars and switches.radius-xson checkboxes, kbd and inline code. - Borders. All borders are 1px. Use
borderfor structure andborder-strongfor controls. A selected outline (tiles, chips) isemerald-deepat 1px. Invalid fields usedanger. - Shadows.
shadow-smon menus and tooltips.shadow-mdon notifications and the focused composer.shadow-lgon modals. Cards at rest have a border and no shadow.
Motion
Section titled “Motion”- Durations. 120ms for hover and colour changes. 200ms for state changes (checked, expanded, caret stroke). 240ms for overlays entering, 160ms for leaving. Easing is
cubic-bezier(.4, 0, .2, 1). Leaving usesease-in. - Press. Buttons and tiles scale to 0.98 while pressed, then return. With the fill change, this is the signal that the control reacted.
- The caret. Progress draws the left stroke, then the right stroke, 200ms each, in ink. Done turns the whole caret
emeraldin one 200ms fade. The loader draws itself in a 1.6s loop (Loader). Working tasks lift 2px every 4s, so you can see an agent is still busy. The caret never spins. - Loading. Buttons show the draw caret in place of their icon and keep their width. Lists show
Skeletonshimmer. Nothing shows a spinner. - Reduced motion. Under
prefers-reduced-motion: reduce, every animation stops. Carets show their final state. Overlays only fade.
Interaction states
Section titled “Interaction states”Every interactive element has all of these states. Each one looks different from the others, in both themes:
- Default and hover (
surface-hoveroremerald-hover, 120ms). - Active / pressed (
emerald-activeor a darker ground, scale 0.98, immediate). - Focus-visible (the
focusring, 2px, 2px offset). Mouse clicks do not show it. Keyboard focus always does. - Disabled (
ink-disabledtext,surface-200ground, no hover,cursor: not-allowed). It usesaria-disabled, so it stays in the tab order, with a tooltip that says why. - Loading (the draw caret,
aria-busy, width held, clicks ignored). - Selected / checked (
surface-selectedground oremerald-deepfill, plus the check mark or caret). - Invalid (
dangerborder,dangermessage with the X icon,aria-invalid,aria-describedbypointing to the message).
Iconography
Section titled “Iconography”- Tabler Icons, outline style, 1.75 stroke. Use
icon-md(20px) in controls and nav,icon-sm(16px) inside badges and inputs, andicon-lg(24px) in empty states and tiles. Icons takecurrentColor. A decorative icon isaria-hidden. An icon-only control has anaria-label. - The caret (
assets/icons/caret.svg) replaces every arrow-up, chevron-up, trending-up and check in the set. “Send” is the caret. So are “Done” and “Scroll to top”. Downward chevrons for menus and selects are fine. They are not the mark. - Status carets (
assets/icons/status-*.svg): idle iscaret-idle(Queued). The left stroke ininkand the right incaret-idle(status-plan) is Preparing and Planning. Both strokes inink(status-build) are Working and Checking. Done isemerald. Failed is an X indanger. Each sits next to its word. - No emoji in UI, no filled icons, no two-tone icons.
- Wordmark: use
assets/logos/upmo-wordmark-light.svgon light grounds and-dark.svgon dark. Use-ink.svgand-bone.svgfor single-ink print. The minimum width is 72px. Keep clear space on every side equal to the caret height. - Mark:
assets/icons/caret.svgalone is the icon, the favicon and the app icon (assets/logos/upmo-app-icon.svg, anemeraldtile with a Bone caret). The minimum size is 16px. - Never colour the caret anything but
emerald,ink,caret-idleor Bone. Never add a drop shadow, outline, gradient or rotation. Never set the wordmark in live text.
Other sections
Section titled “Other sections”The other sections go deeper:
- Accessibility: the checklist every component passes.
- Forms: interactive validation, steps, long forms.
- Patterns: navigation, overlays, feedback, empty and loading states.
- Mantine: the theme object and the component mapping.
- Data visualisation: chart forms and validated palettes.
- Microcopy: every string, with
microcopy.jsonas the source. - Icons: the concept map,
icons.json. - High contrast and colour vision: the
hctheme, forced colours, the simulation report. - Responsive: breakpoints, shell, overlays, touch.
- Motion: the tokens and the catalogue.
- Marketing kit: site register, words, assets, templates.
The Foundations group holds review tools, not product components: IconMap, ColorVision, Breakpoints, Motion. Exports holds PNG and ICO versions of the logo, for places that cannot use SVG.