# The Mode Contract

A **theme** is a named look someone has saved and can carry with them — a set of style choices (its color, its typography, or both), complete for each part it includes. Applied to a scope, a theme becomes a **mode** — a look put in place. The point of this standard is that modes are **predictable**: given the same page and the same viewer, every conforming app resolves a look the same way. That doesn't always mean everyone sees an identical picture — a viewer who brings their own preference sees *theirs*, on purpose; that's the heart of Mise en Mode. What's guaranteed is the *rules*: a look renders faithfully wherever nothing more-preferred supersedes it, and where the viewer has asked for their own, that's applied instead — resolved the same way everywhere. A schema alone can't promise this: it fixes the *shape* of the saved data, not what an app *does* with it. This document is the missing half — the rules an app follows so a look resolves the same way no matter who renders it.

**Who this is for.** If you're just curious what a theme is, you don't need this page — read the introduction. This page is for people building an app that reads themes and applies them. It's written plainly, but it's precise on purpose: the parts in boxes are the rules you must follow to be conforming.

Every rule below is running in the [live demo](https://standard.mode.place/demo/) — toggle the sources and watch the cascade resolve them.

Throughout, the rules use the keywords **MUST**, **SHOULD**, and **MAY** in the usual sense: **MUST** is required for a conforming app, **SHOULD** is strongly recommended, **MAY** is optional.

## Words we use

A small vocabulary keeps everything below precise. Each is a plain idea first; the technical anchor (for those wiring this into the AT Protocol ecosystem) is in parentheses.

- **A theme** — one saved look: its values for color, typography, or both — complete for each aspect it includes. Plainly, "a look." (A `place.mode.standard.theme` record.)
- **An intent** — *what a value is for*, not a raw color or size: the background of an action, the text of a surface. The current core is 33; the set grows over time (see below).
- **An aspect** — a slice of a look: its **color** (currently 21 intents) or its **typography** (currently 12 intents).
- **A scope** — a region of the page a look is applied to: the whole page, or a single card, section, or widget.
- **An expression** — the concept a look is *for*, named so different people's versions line up — "critical," "Easter," or a personal "midnight." Every theme serves exactly one, and it *is* the theme's address. (A theme's **record key** — `…/theme/dark` serves `dark`.)
- **A mode** — a theme *applied* to a scope for its expression: a look put in place, by marking that scope's element with a `data-mode` attribute and setting the theme's variables there — not a saved thing.
- **The viewer** — the person looking at the page, who may have a look of their own.
- **A preference** — the viewer's baseline look: their theme saved at the record key `default`. (A `place.mode.standard.theme` keyed `default`.)

**The core grows.** Today the core is 33 intents across two aspects (color, typography). The standard is built to add more over time — new intents, new purposes, even new aspects — always as *optional additions*, so older themes stay valid. So an app should emit whatever a theme actually contains (Rule 1 turns *any* intent into a variable by its path) rather than hard-code a list of 33.

---

## Rule 1 — Every value becomes a CSS variable named after where it lives

A theme's values live under `aspects`, nested by aspect. To use one, turn each value's *path from its aspect* into a CSS custom property name: read from the aspect down and join the segments with dashes.

```
color → surface → primary → background   →   --color-surface-primary-background
typography → text → auxiliary → lineHeight → --typography-text-auxiliary-line-height
```

That's the whole rule. The `aspects` container that groups them is structural — it isn't part of the name — and a two-word segment splits on its capital (`lineHeight` → `line-height`).

> **Rule.** For each intent value, an app **MUST** name its CSS custom property by joining the value's path *from its aspect* (`color`, `typography`, …) with `-`, lowercasing and kebab-casing each segment; the `aspects` container is not included. An app **MUST NOT** abbreviate, reorder, prefix, or otherwise alter these names.
>
> **Why it's strict:** identical names across every app are the entire reason a look can move between apps. The moment two apps name the same value differently, a shared look stops working — one app's page can no longer pick up another's theme.

## Rule 2 — Fonts are declared before the text that uses them

Fonts are **theme-level assets** — they live in the theme's top-level `fonts`, *beside* `aspects`, not inside typography. They sit apart because an `@font-face` is document-global: it can't be scoped and isn't a variable, so it's a different kind of output from the intents that become scoped variables. A text intent points at one by its family name. (Only text uses fonts, so a color-only theme — or one using only system fonts — carries none.)

> **Rule.** An app **MUST** emit an `@font-face` declaration for each entry in the theme's `fonts` — **once per entry**, document-global (not per scope) — so the font is loading by the time a `text.*.family` value references it. A `text.*.family` value is matched to a font entry by its `family`; a family shared by several text intents (say body and detail) is declared only once. A referenced family with no matching entry is treated as an ordinary CSS `font-family`, and normal font fallback applies.
>
> A font entry **MUST** carry a `family` and at least one source — a `src` (a URL) or a `file` (the font saved with the person's account); every other descriptor (`weight`, `style`, `stretch`, `unicodeRange`, `display`) is optional, so a minimal entry is just `{ family, src }`. If both sources are present, the app **MUST** prefer `file` — it travels with the person and can't rot. (The descriptors are the common CSS `@font-face` set; more may be added over time as optional fields, never a breaking change.)

## Rule 3 — Applying a look to a scope

Applying a look has two halves that meet at the scope's element:

1. **The marker.** The scope's element carries a `data-mode` attribute whose value is a theme's **expression** — its **record key** — `<main data-mode="dark">` marks the scope for the theme saved at `…/theme/dark`. That equality *is* the matching mechanism: a theme applies at a scope exactly when the scope's `data-mode` token equals the theme's expression (its rkey), byte for byte (Rule 4 layers optional `/aspect` selection on top). The marker is declarative markup, written where the scope is (by whoever builds the page), and it is the *only* thing the page has to say about placement.
2. **The variables.** The theme's values are set in a rule whose selector matches that marker — `[data-mode="dark"] { --color-…: …; }` — so they land **on the marked element** and inherit inward through ordinary CSS. The app's own styles then read those variables by their Rule 1 names.

Splitting it this way is what makes late binding work: the marker is fixed at build time, but a look's variables can be added — even a viewer's, fetched after the page loaded — by appending one rule that finds every matching marker. Nothing has to walk the DOM or inject at each scope.

```html
<style>
  [data-mode="dark"] { --color-surface-primary-background: …; /* every intent */ }
</style>

<main data-mode="dark">…</main>   <!-- the whole subtree is "dark" -->
```

> **Rule.** To apply a whole theme, an app **MUST** (a) mark the scope's element with a `data-mode` attribute carrying the theme's expression (its record key), and (b) set every intent of each aspect the theme carries — as custom properties by their Rule 1 names — in a rule that matches that marker, so the values land on the marked element and inherit into the scope. A mode is always a deliberate placement — an app **MUST NOT** set a theme's variables on the document root (`:root` / `<html>`) as an implicit global default; a `data-mode` marker is a placement, `:root` is not. (A whole-page look marks a top-level content element such as `<body>` or an app-root container; a smaller scope marks that container.) Each aspect a theme carries is complete — see Rule 6.
>
> Fonts (Rule 2) are document-global — their `@font-face` declarations are emitted once, outside any `data-mode` rule, not per placement.

:::note[Why: a marker + cascade layers, not scoped placement]
An earlier design applied a look by **placement** — scoping its CSS with a position-based `@scope` and dropping it at the target element, so the tag's DOM position *was* the scope, with no marker. It was reversed once the lifecycle was clear. A look's boundaries are known at **build time**, but the **viewer's** look arrives *later* (after sign-in) — and position-based placement meant injecting a `<style>` at *each* scope root at runtime to apply it. A `data-mode` **marker** lets the author declare boundaries once, so the viewer's look becomes a **single `<style>` appended to `<head>`** that the cascade routes to every matching marker — no DOM walking, no per-scope injection, no flash when server-rendered. Precedence then rides **cascade layers** (`@layer app, author, viewer`) rather than the app hand-picking a winner per scope. Since a look only ever sets *inherited* custom properties, the selector-scoping `@scope` offered was never needed here — "set variables at a marker, inherit down" is the whole mechanism — and attribute selectors reach further back than `@scope` does. (`@scope` keeps one job: the containment donut in Rule 7.)
:::

## Rule 4 — Selecting one aspect instead of the whole look

Sometimes only part of a look should apply to a scope — its color but not its type. That choice rides the `data-mode` marker itself: the expression, optionally qualified by an aspect after a slash. `data-mode="pink"` is the whole look; `data-mode="pink/color"` is just its color. (A viewer pins their *baseline* to one aspect a different way — by making their `default` theme itself carry only that aspect; there is no pointer to fragment.)

Every *expression* named inside `data-mode` is a theme's **record key** — the string a scope and a theme match on. A `data-mode` value has exactly **two legal shapes**, never mixed:

- **One bare expression** — `data-mode="dark"` — the *whole* look: every aspect, including any the standard adds later.
- **A whitespace list of aspect fragments** — `data-mode="easter/color pink/typography"` — each `expression/aspect`, assembling a look from parts, **at most one fragment per aspect**.

```html
<main data-mode="pink">                <!-- whole look -->
  <section data-mode="easter/color">   <!-- color only; typography keeps cascading from "pink" -->
    …
  </section>
</main>
```

The slash is the aspect delimiter (`pink/color`), and it is deliberately a character that **cannot appear in a record key** (the rkey charset is `A-Za-z0-9._~:-`) — so no expression can ever contain it, and a dotted expression name (`com.homedepot.easter/color`) parses unambiguously. A fragment list is disjoint **by construction** — each fragment names its aspect, so distinct aspects never collide and their order is irrelevant.

> **Rule.** A `data-mode` value is either a single bare expression (the whole look) or a whitespace-separated list of `expression/aspect` fragments (each aspect appearing at most once); an app **MUST NOT** mix the two forms or list two bare expressions. A rule that applies an aspect **MUST** set only that aspect's variables (color → the `--color-*` variables; typography → the `--typography-*` variables) and **MUST NOT** set the others; the omitted variables keep inheriting from the enclosing scope.
>
> **How a theme's rule matches both forms.** For each aspect a theme carries, an app emits one rule whose selector accepts the whole-look marker *and* that aspect's fragment:
>
> ```css
> :where([data-mode="dark"], [data-mode~="dark/color"])      { /* the --color-* set */ }
> :where([data-mode="dark"], [data-mode~="dark/typography"]) { /* the --typography-* set */ }
> ```
>
> The exact `[data-mode="dark"]` arm fires for the bare whole-look marker; the `~=` (whitespace-token) arm fires when `dark/color` appears as a token, alone or among other fragments. So `data-mode="dark"` picks up every aspect, `data-mode="dark/color"` picks up only color, and a fragment list picks up exactly the aspects it names.
>
> **Why it works cleanly:** the two aspects never share a variable name, so applying one leaves the other exactly as the ancestor set it. Nothing is overwritten or merged — the example above shows Easter's colors over Pink's typography purely because only the `--color-*` rule matched.

**Bare vs. fragments differ on future aspects.** `data-mode="dark"` means the *whole living expression* — when a new aspect (say motion) is added, a bare-`dark` scope picks it up automatically. `data-mode="dark/color dark/typography"` is *pinned* to those two named aspects; a later `dark/motion` won't apply there. Bare is the whole look; fragments are exactly these slices.

:::note[Why: two forms, never mixed]
The grammar is deliberately either/or so that **ordering never matters**. An earlier idea let a value list several bare expressions (`dense dark`), which immediately raised "which wins, and does `dense dark` mean the same as `dark dense`?" — ambiguity you'd have to *patch* with precedence rules. The two-form grammar designs it out: a bare expression is one whole look, and a fragment list is **disjoint by construction** (each fragment names its own aspect, so distinct aspects can't collide and order is irrelevant). And the slash delimiter is chosen from **outside the record-key charset**, so no expression can ever contain it: the parse is unambiguous by construction, not by convention — a dotted reverse-DNS expression (`com.homedepot.easter/color`) can't be misread, and no rkey is ever unaddressable. (An earlier draft used a colon, but `:` is a legal rkey character — which forced convention-only "don't use a colon" rules and reserved words the grammar couldn't actually enforce.)
:::

How expressions are otherwise spelled — a bare word like `dark` for a common concept, a reverse-DNS string like `com.homedepot.easter` for your own — is a **naming convention, not a rule** (see [Common expressions](https://standard.mode.place/reference/expressions/)). Because the two delimiters `data-mode` parsing relies on — the slash (aspect) and whitespace (fragment separator) — sit outside the record-key charset, **every valid record key is automatically a safe `data-mode` token**: there is nothing to avoid, and no expression name is reserved except `default`. *(Examples here use short bare names for readability; a real brand or personal expression would be namespaced.)*

## Rule 5 — Whose look wins: fill-through, not conflict

A scope can have more than one look available for it: the viewer's own, the content author's, the app's. They don't blend. Instead, **each variable at a scope is filled by the most-preferred source that sets it**, and anything unfilled falls through to the next source — and ultimately inherits from the enclosing scope.

Two ordinary CSS mechanisms do all the work, and it's worth being clear about which does which:

- **Cascade layers** rank the *sources*. An app declares the order once — `@layer app, author, viewer;` — and emits each source's `data-mode` rules into its layer. A later layer wins, so where two sources set the **same variable on the same marked element** (both have a `dark`, both matching `[data-mode="dark"]`), the viewer's beats the author's beats the app's — per variable, with anything a winning layer leaves unset falling through to a lower one.
- **Inheritance** handles *locality*. Because each look is set on its own marked element, a value set directly on a nearer element beats one merely inherited from an ancestor — regardless of layer. So a baseline placed high up fills everything below it *except* where a more-local scope sets that variable itself.

:::note[Why: `:where()`, and what layers vs. inheritance each do]
Look rules are wrapped in `:where(…)`, which contributes **zero specificity** — on purpose. It takes specificity *out* of the contest so **layer order alone** decides when two sources meet on the same marker; without it, a more-specific selector could beat a higher layer and break the precedence. The division of labor is then exact: **layers rank the tiers** (app < author < viewer) but *not* peers within a tier — two authors both in `@layer author`, both keyed `default`, aren't separated by the layer, which is exactly why a multi-author feed needs containment ([Rule 7](#rule-7--keeping-a-foreign-look-contained)). **Inheritance** handles locality — a baseline set high up fills everything below that no nearer scope re-sets.
:::

Together these give the precedence, most-preferred first:

1. **The viewer**, when they have a look that applies here (see below).
2. **The content author**, when the content carries its own look.
3. **The app's** own look.
4. **Inherited** from the enclosing scope; failing that, the app's bare, un-moded styling.

The viewer "has a look that applies here" in one of two ways:

- **Their default look** is their baseline — the theme they saved at the reserved record key **`default`** (see [Common expressions](https://standard.mode.place/reference/expressions/#the-one-reserved-word-default)), emitted at a top-level scope marked `data-mode="default"` (in the `viewer` layer), inheriting into everything below that no more-local scope re-sets. Its own aspects decide what it covers.
- **A matching look**: when a scope is marked for a concept (`data-mode="critical"`) and the viewer has saved a theme *at that key* (`…/theme/critical`), the app emits the viewer's look into the `viewer` layer targeting that same marker — so it wins there over the author's and app's.

Matching is exact, and the `data-mode` string is where it happens: the viewer's "My Critical" stands in for the app's `critical` only because both are saved at record key `critical`, so both render to `[data-mode="critical"]` — not because their names look alike, and not for a coincidentally similar one.

**One look can serve several names — but that's the owner's tooling, not the protocol.** A theme serves exactly one expression (its key), so a look that should answer to several markers — a `dark` that is also your `default`, or that also overrides `com.homedepot.dark` — is saved as several *self-contained records*, one per key (`…/theme/dark`, `…/theme/default`, `…/theme/com.homedepot.dark`), each carrying **the same values**. Keeping those copies in sync is a job for the theme owner's authoring/build tooling — its single source of truth compiles to one record per key. The protocol stores independent records keyed by expression; it neither models nor enforces the aliasing (there are no references between theme records, so nothing can dangle). This is not composition either: a look that genuinely *combines* two concepts is one theme saved at one composite key — `…/theme/com.homedepot.easter-dark` — never a blend. One record is always one complete set of values.

> **Rule.** An app **MUST** rank sources with cascade layers in the order `app, author, viewer` (least- to most-preferred), emitting each source's rules into its layer, so that per variable the most-preferred source that sets it wins and the rest fall through. An app **MUST NOT** blend two sources' values for the same variable by any other means, and **MUST NOT** place any source's look outside its layer (an unlayered rule would beat every layer). Two looks match (viewer stands in for app) only when their themes have the **same record key** — the expression, byte-for-byte equal — which is exactly when they render to the same `data-mode` marker. A theme serves one expression (its key); one look answering to several markers is several self-contained records the owner's tooling keeps in sync, never a composite blend. An app **SHOULD** resolve and apply the viewer's look when it can identify the viewer; honoring the viewer is what makes the standard worth adopting.
>
> **Worked example.** The viewer prefers `ecommerce`; they're on a Home Depot page. The app sets the viewer's `ecommerce` at a top-level scope (viewer layer) as their baseline. Inside, the app marks a section `data-mode="easter/color"` and emits Home Depot's Easter into the `app` layer. At that section: its **color** is set directly on the section by the app's Easter rule, so it beats the `ecommerce` color merely *inherited* from above — the viewer has no Easter, so nothing in the `viewer` layer sets it there. Its **typography**, which nothing re-set on the section, keeps inheriting from the viewer's `ecommerce`. Home Depot's Easter colors, the viewer's type, no merge — and had the viewer *had* an Easter look, the app would emit it into the `viewer` layer at that same `data-mode`, and it would win.

## Rule 6 — Aspects are optional, but each is complete

A theme's `aspects` carry color, typography, or both — it needn't fake an opinion it doesn't have. But whichever aspect it *does* include is fully defined: every intent of that aspect is present. That's what lets a present aspect fully paint its slice and lets fill-through work by simple inheritance — there's never a *half-aspect* that leaves gaps to reason about.

> **Rule.** An app **MUST** treat an aspect as valid only if it defines all of that aspect's intents (currently color = 21, typography = 12 — counts that may grow in later versions; the schema defines the current set, and the author's tooling validates against it before publishing). An aspect that is present but missing any value is invalid — an app **MUST NOT** partially apply it; it ignores the invalid aspect and falls through to the next source in Rule 5. An aspect a theme *omits* is not invalid: it simply isn't provided, and that scope's other sources (or the cascade) supply it. (A theme SHOULD carry at least one aspect; one with none paints nothing.)

## Rule 7 — Keeping a foreign look contained

Because *every* look emits the same variable names (Rule 1 — the very thing that makes looks portable), applying a content author's look to a scope restyles **everything** in that scope that uses those names — including the app's own buttons and controls if they happen to render inside it.

Picture a blog post that carries its author's look, shown inside a feed reader. The post's look should dress the post — not quietly repaint the reader's own toolbar sitting next to it.

> **Rule.** When an app applies a look that isn't its own (a content author's, or a viewer's look over foreign content), it **MUST** confine that look to the DOM subtree that is purely that content. The app **MUST** keep its own chrome — navigation, action bars, controls — outside that subtree, or re-apply its own look to that chrome. An author's look is meant for content surfaces and text, not the host's controls.
>
> Marking the content element with `data-mode` contains a look **only when its marker is unique to that content** — a namespaced `com.alice.dark` matches only Alice's subtree, so its variables never reach anyone else's. But when several sources **share** a marker — a feed where every author's post is dressed in its own `default` — one author's `[data-mode="default"]` rule matches *every* post, and inheritance can't save you (the rule sets the variable directly on each). An app **MUST** bound each such look to its own subtree with [`@scope`](https://developer.mozilla.org/en-US/docs/Web/CSS/@scope): writing the look's `<style>` *inside* the post lets a prelude-less `@scope { … }` root at that post automatically (no selector needed), or use `@scope (selector)` when the CSS lives elsewhere. Either form restricts *which* elements the rule matches **without adding specificity**, so the `@layer` order is untouched and a viewer's page-global look still overrides inside the scope. (Separately, for host chrome that must render *inside* a look's subtree yet keep the host's own look, re-mark it with the host's `data-mode`, or use `@scope (…) to (…)` to carve out a donut.)
>
> A safety note that costs nothing to state: a look can only ever set the standard's known intents to CSS-string values. It can't inject arbitrary CSS or script. Containment is about *layout and legibility*, not untrusted code.

## Rule 8 — Nesting

Scopes nest, and so do looks. An inner `data-mode` re-declares the variables for its subtree, so its values apply within it; anything it doesn't set keeps inheriting from the scope around it. No containment machinery is needed for the ordinary case — a light scope inside a dark one just re-sets the variables it changes.

> **Rule.** An app **MUST** let nested scopes resolve through ordinary CSS inheritance: a variable set on an inner scope (a nested `data-mode` marker) applies within it, and a variable it doesn't set inherits from the nearest enclosing scope that did. Apps **MUST NOT** implement a separate merge or precedence engine for nesting — the cascade is the mechanism.

## Putting it together — resolving one scope

Each rule above governs one facet. Here's how they combine into the single procedure an app runs for every scope: first where the candidate looks come from, then the steps, then a full trace.

**Where each source's look comes from:**

- **The viewer** — you first need to know *who* they are: their AT Protocol identity (their DID), which an app typically obtains when the viewer signs in (AT Protocol OAuth). Their candidate for a scope is a theme of theirs saved **at the scope's concept key** (`…/theme/critical`) — or, for the page root and any scope no concept has claimed, their **default look** (the theme they saved at the reserved key `default`; its own aspects decide what it covers).
- **The author** — if the content you're rendering carries its own theme reference (a publication that points at one), that theme.
- **The app** — your own theme for the scope's concept, or your default look.

**The setup**, done once: declare the layer order `@layer app, author, viewer;`. **The procedure**, run per scope:

1. **Mark what the scope expresses** — a `data-mode` attribute on the scope's element (the page root, plain content, or a concept the app or author places there). Author markup carries these for its own content; the app adds them for concepts it places.
2. **Gather the candidate themes** above (skip any that don't exist): the viewer's matching theme and preference, the author's, the app's.
3. **Emit each source's themes into its layer** — `app`, `author`, or `viewer` — each rule targeting its `data-mode` marker by exact name (Rules 1–3), emitting only the aspects it carries (Rule 4). Set the viewer's preference at a top-level scope as their baseline.
4. **Let the cascade resolve.** Layers pick the winner per variable where sources compete on the same marker; inheritance fills each scope from the nearest source and lets a baseline flow into everything unclaimed (Rules 5, 8). Confine any foreign look to its content subtree (Rule 7).

> **Rule.** An app **MUST** resolve every scope this way — ranking sources by layer and letting the cascade fill **each variable independently** from the precedence order. It **MUST NOT** pick a single source for a whole scope, nor implement a separate merge engine — a scope's color and typography may legitimately come from different sources, and the cascade is the mechanism. Two looks are the "same concept" (viewer stands in for app) only when their themes share the **same record key** — the expression, byte-for-byte equal.

**A worked trace (multi-party).** A viewer whose baseline `default` look is **color-only** — its `default` theme carries color but no typography — reads Alice's post, published with her own tools and rendered inside a feed app. Alice's post carries her theme `alice-zine` (color + typography). At the post scope, each aspect resolves on its own:

| Aspect | viewer | author (Alice) | app | → applied |
|---|---|---|---|---|
| **color** | `default` ✓ | `alice-zine` | app default | **viewer's `default`** |
| **typography** | — (color only) | `alice-zine` ✓ | app default | **Alice's `alice-zine`** |

The post renders in the viewer's **colors** and Alice's `alice-zine` **typography** — two sources, one scope, no blend, chosen aspect by aspect. Here the two sources meet on the *same* marker: at the post element the app emits Alice's `alice-zine` into the `author` layer and the viewer's baseline color into the `viewer` layer. For `--color-*`, both layers set it, so the higher `viewer` layer wins; for `--typography-*`, only the `author` layer sets it (the viewer's `default` carries no typography), so Alice's comes through. Alice's look is confined to the post subtree (Rule 7); it doesn't leak into the feed app's surrounding chrome. Had the viewer's `default` carried typography too, the `viewer` layer would set it and Alice's wouldn't appear here — an author's look shows wherever the viewer hasn't expressed their own, by design. (A viewer pins their baseline to one aspect by making the `default` theme itself carry only that aspect — the pin is the theme's own composition, there is no separate pointer to fragment.)

**One remaining detail.** Because every value is only a CSS string, a malformed one simply doesn't render (like any invalid custom property); an app needs no special validation and **MUST NOT** reject a whole theme over one bad value — that value just has no effect.

## Who's responsible

The standard checks that values are well-formed strings. It can't check that a look is *readable* — that its colors have enough contrast, that its text is legible.

> Whoever **authors a look's values owns it** — including responsibility when it fails someone. What makes that fair is the viewer's guaranteed ability to apply their **own** look instead: nobody is ever trapped in a look that doesn't work for them. For that safety valve to hold, a conforming app **SHOULD** honor the viewer's look when they have one (Rule 5). Author-owns-accessibility plus the viewer's guaranteed override together form the accountability model — without the standard having to police accessibility itself.
>
> That override reaches even a *specific, branded* concept. Because looks match by exact record key, a viewer who needs a different take on a particular look saves a theme *at that concept's key* — a brand's own `…/theme/com.homedepot.easter`, carrying the values they want there — and a conforming app applies the viewer's version wherever the concept is placed. (Their tooling can generate it from a look they already have; on the wire it's a distinct record keyed by the brand's concept.) A namespaced concept is therefore *reserved, not locked*: a generic viewer preference slides past it, but a deliberately-saved one supersedes it. The more generic the concept an app places (bare `dark`), the more readily it invites a viewer's look; the more specific (`com.homedepot.easter`), the more deliberate the override must be — but it is always available. (See [Common expressions](https://standard.mode.place/reference/expressions/#generic-invites-specific-reserves).)

## What an app may decide for itself

- **How to deliver the CSS** — the standard fixes the variable *names* (Rule 1), the `data-mode` marker and matching rule (Rules 3–4), and the layer ranking (Rule 5), not how the bytes reach the page. An app **MAY** write the rules in an inline `<style>` (server-rendered into the HTML for no flash, or appended to `<head>` after the viewer signs in), or serve them and `<link>` them — as long as the variables land on the marked element (never `:root`), each source sits in its layer, and the aspect selectors are as Rule 4 specifies. Any approach that puts the same names at the same markers in the same layers conforms. (`@scope` remains available as optional sugar for the containment donut of Rule 7.)
- **Where** to honor an author's look — an app **MAY** apply it in a detail view but not in a dense feed, if that reads better.
- **Interactive states** — resting values are all the standard defines; an app **MAY** derive hover/focus/pressed from them however it likes.
- **Whether** to honor viewer looks at all — an app that only ever uses its own look is still a valid consumer; it simply forgoes most of the standard's value.

## Conformance at a glance

An app is **conforming** when it:

- **MUST** name variables exactly by schema path (Rule 1); declare fonts once, document-global (Rule 2); apply a look by marking its scope with `data-mode` and setting the variables on that element, never `:root` (Rule 3); honor the two-form `data-mode` grammar and set only the selected aspect's variables (Rule 4); rank sources with `@layer app, author, viewer` so each variable fills from the most-preferred source that sets it, match concepts by record key (a theme serves one expression — its key; a look answering to several markers is several owner-synced records, never a blend), and never place a look outside its layer (Rule 5); treat each aspect as valid only if complete, and never partially apply an incomplete one (Rule 6); contain foreign looks to their content (Rule 7); resolve nesting through inheritance (Rule 8); and resolve every scope by layer and cascade — never picking one source for a whole scope (§ Putting it together).
- **SHOULD** honor the viewer's look when it can identify the viewer (Rule 5); keep its own chrome out of foreign-look subtrees (Rule 7).
- **MAY** choose where to honor author looks, derive interactive states, deliver the CSS inline or as served files, use `@scope` for containment donuts, and — though it forgoes the point — ignore viewer looks entirely.