Docs menu
DocsBuild an app

Styling & theming

The two stylesheets ChatOSS injects into every app, the design tokens, and the three rules every app must follow.

ChatOSS injects TWO stylesheets into every app frame, before your own CSS, in this order:

  1. appTheme.css — the design tokens as CSS custom properties: --bg, --surface, --surface-2, --border, --text, --text-2, --muted, --faint, --accent, --accent-fg, --accent-soft, --link, --danger, --danger-fg, --green, --amber, --blue, --purple, --radius, --sans, --mono, … These are the SAME names and values the built-in ChatOSS sections use, so an app styled with them is literally the same colour as the OS around it.

  2. appUi.css — the platform component classes:

    • Layout: .app-shell / .app-shell--two / .app-shell--plain, .app-sidebar, .app-sidebar-label, .app-main, .app-aside, .app-titlebar, .app-title, .app-body, .app-footer, .toolbar, .spacer
    • Buttons: .btn .btn-primary .btn-danger .btn-ghost .btn-sm .btn-icon .btn-row; segmented controls: .seg / .seg-item
    • Forms: .field .field-row .label .hint .input .textarea .select .checkbox
    • Lists: .list .list-row .list-row-title .list-row-meta
    • Chat: .chat .msg .msg-role .bubble .msg-body .composer; tool blocks: .tool / .tool-head / .tool-name / .tool-status / .tool-body
    • Misc: .badge (+-success/-warn/-danger/-info), .chip .chip-dot, .statusline .spinner .card, .overlay .modal .modal-head/-body/-foot .modal-close, .empty/.empty-title/.empty-text, .code-block, .muted .faint .mono .truncate .nowrap
    • Bare <input>/<textarea>/<select>/<button>/h1h4/a/hr are styled too, so plain semantic HTML already looks right.

The three rules

🔴 Every app in the ChatOSS app catalogue had to be repaired for breaking these.

1. Build your UI out of the platform classes and tokens first

Reach for class="btn btn-primary" and var(--surface-2), not a hand-rolled palette. Your own CSS loads LAST and nothing is namespaced, so you can still override any platform selector — but override, don't replace.

2. NEVER redefine the colour tokens on :root

/* 🔴 NEVER do this: */
:root {
  --bg: #fff;
  --text: #111;
}

That overwrites the host's palette for your frame only, so your app freezes in one theme while the rest of the OS switches, and it strands you on the wrong text/background pairing. Define your OWN new variables under an app-specific name if you need extras (--myapp-note-yellow), never the platform names.

3. NEVER use @media (prefers-color-scheme: dark)

The host does not follow the OS preference — it follows the ChatOSS Light/Dark/System setting, which it publishes by stamping data-theme="light" or data-theme="dark" on your document's root element (and re-stamping it live when the user changes it). A prefers-color-scheme rule therefore fires at the wrong times: a user in explicit Light mode on a dark-mode machine sees your app go dark while every other app stays light. If you genuinely need a per-theme rule of your own, key it off the stamp:

[data-theme='dark'] .my-thing {
  /* … */
}

Almost always you need no such rule at all — style with the tokens and both themes come for free.

Check both themes

Never hardcode #fff as the text on an --accent surface: --accent is near-black in light and near-white in dark, so use its paired var(--accent-fg).