Skip to main content
This documentation carries three kinds of statement, and they are not interchangeable: A divergence is not a bug report and not a wish. It is a place where building straight from the code would produce something the design language does not want, or where the language describes something the code does not yet provide. Each entry names both sides and says which to follow today. Two kinds of disagreement land here, and they resolve differently. When code and the design library each state a value, code is authoritative and the design library has drifted — the library is an emission target and is being rebuilt from code. When the design library states a composition — how regions nest, what a template is made of, which slots a page has — code is usually not disagreeing but silent, because the frame has no way to say it. There the design library carries the intent and code is the side that has to grow. TITAN-DIV-16 is the second kind; TITAN-DIV-01 is the first.
Cite the ID. When a divergence drives a decision, name it — “chip tint is Tier-3 by design per TITAN-DIV-01.” An agent or reviewer can then check the reasoning against the system instead of against taste. IDs are stable and never renumbered.

How to use this page

  1. Build to design intent, not to what the code happens to do.
  2. When they conflict, follow the Follow today row — it is the safe answer right now.
  3. Cite the divergence ID in the PR or the design rationale.
  4. If a divergence is resolved, the entry stays and gains a resolution note. Deleting it loses the reason the code looks the way it does.

TITAN-DIV-01

Tier-3 tokens alias primitives, never semantics. The stated rationale for “never skip semantic” is that a component token pointing at a primitive cannot resolve correctly when the theme changes. That rationale does not hold here — every one of the 26 resolves correctly, because each declares its own values. The real cost is different, and smaller: each token re-decides independently what the semantic tier already decided, and its relationship to the token it was derived from exists only in prose.

TITAN-DIV-02

border-focus is specified and was never implemented. This is the most consequential gap in the token layer, and it is not an open question. TITAN-A11Y-04 requires a visible focus indicator; the specification supplies the token to do it with; the code does not have it. Every component deciding focus independently is the cost of that one missing implementation.

TITAN-DIV-03

Status colors fail contrast on light surfaces. The status tokens invert their safety between modes: text-success fails badly in light (2.41:1) and passes comfortably in dark (6.80:1). A component verified in one mode carries an undetected failure in the other. Measured values are in the contrast tables.

TITAN-DIV-04

Three typography surfaces, three different counts.

TITAN-DIV-05

The SCSS token surface is frozen at 82 names.

TITAN-DIV-06

Two tokens for zero.
The recorded reason does not hold. spacing-none is described as being for resets that must stay 0 regardless of context — but spacing-0 is also always 0. The distinction exists in prose and nowhere in the values.

TITAN-DIV-07

No surface layering model.

TITAN-DIV-08

No grid specification.

TITAN-DIV-09

The naming specification and the shipped tokens have substantially diverged.
Both sides are legitimate. The specification is the design baseline, confirmed by its owner. The shipped tokens are what renders. Neither is drift from the other in a simple sense — they grew apart, and 54 of 244 overlapping is the measure of how far.This is why the grammar is documented separately from the inventory. The grammar transferred intact; the token list did not.

TITAN-DIV-10

Documentation groups by concept; the package exports by unit. The documentation was already doing this implicitly. 19 of 39 component pages in the navigation have no matching Titan folder. Most are simple, harmless aliases — no second export competes for the design-vocabulary name: Table is DataGrid, Switch is Toggle, Tag is Chip, Input is TextField, FileUpload is Dropzone, Spinner is CircularProgress, ProgressBar is LinearProgress. Two are not simple aliases and do not belong in that list. Dialog and Link each collide with a second, real, unrelated export that also compiles under the design-vocabulary name — importing that second export silently produces something dangerously incomplete (Dialog) or entirely different (Link). These are dual-export collisions, not naming aliases: see TITAN-DIV-27 for Dialog/Modal and TITAN-DIV-25 for Link/TextLink.
Switch/Toggle confirmed, not inferred, as of this pass. The Figma library’s own component is named Toggle, and Titan’s component tokens use the toggle-* prefix (toggle-track-width, toggle-thumb-size, etc.) — both independent of this documentation’s earlier guess. Unlike the general pattern this entry describes, this is not “the design library’s word standing in front of code’s”: the design library and the token layer agree with each other, and “Switch” is this documentation’s own choice — closer to the Table/DataGrid case than to the rest of this list. Kept as “Switch” regardless, as a plain English word at least as common as “Toggle” — see Switch.
Where the alias comes from matters. Most of these are the design library’s word standing in front of the code’s. Table is not — code and the design library both say data grid, and table is what the team says out loud. That is the vocabulary rule working as intended: the reader-facing name is the decision name, and it is the team that holds it.
This grouping also hides a real asymmetry. Within one concept, Button is a wrapper that adds nothing, IconButton is a bare re-export, and FilterButton is a full Titan component with its own tokens. They carry very different amounts of Invoca decision, and the Exports table is where that stays visible.

TITAN-DIV-11

The design vocabulary names prominence; the code names the treatment.
Prominence survives a visual change; treatment does not. “Primary” names the decision — this is the action the view exists to enable. “Contained” names one way of drawing it. If primary buttons stopped being filled tomorrow, TITAN-BTN-01 would still say the right thing, and a rule written against contained would not.
tertiary is specified and implemented nowhere. The prominence axis lists it and no shipped token carries the word — the treatment exists only as variant="text". It is one of the 215 specified-but-never-shipped names in TITAN-DIV-09.
primary means three unrelated things. Prominence (the highest-weight button), colour intent (color="primary", the brand blue), and text emphasis (text-primary, the default body colour). Prominence and colour are independent axes — a secondary button can be color="primary", and a primary button can be color="error". Any page using the word says which sense it means.

TITAN-DIV-12

The frame’s regions carry wrong or missing landmark roles.
This is a live conformance failure, not an open question. A page footer announced as navigation is wrong, and a page with no main cannot be skipped to. It is the view tier’s responsibility and only the view tier can fix it.

TITAN-DIV-13

A sticky header and a sticky footer sit 1090 stacking steps apart.
The asymmetry is probably not deliberate. Nothing records a reason for the footer needing to outrank a modal scrim, and the two regions are otherwise treated as a pair throughout the frame. Recorded as a divergence rather than a defect because “both on the sticky plane” is a design decision to confirm, not an obvious bug fix.

TITAN-DIV-14

TabPanel names a tab that nothing exported produces, with ids that collide.
An unresolved aria-labelledby is silent. Nothing renders differently, no console warns, and the tab still switches. The only reader who notices is the one whose screen reader announces an unnamed panel — which is why this survived into the shipped export.

TITAN-DIV-15

TabPanel wraps whatever it is given in a text element, and discards the inactive section.
Unmounting is a data decision disguised as a rendering one. A half-filled form in the tab you just left is gone, and nothing warned the person who left it. That is the same class of loss the frame protects against elsewhere — which is why it is recorded here rather than treated as a performance choice.

TITAN-DIV-16

The header has no tab-bar slot, so a tabbed page composes one outside it.
What the workaround costs is ownership. Once the bar is a sibling, the header’s bottom boundary is produced by whatever the page put next to it, which is why the gutter has to be re-supplied by hand — and why a page that forgets to looks identical to a page that chose not to.

TITAN-DIV-17

The page divider belongs to no region.
This is a composition claim, not a measurement. The gap sizes in the current design library are not the argument and will be restated when it is rebuilt from code. The argument is which region owns the rule: a region can swap its own bottom edge for a tab bar, and cannot swap a rule that belongs to neither side of it (TITAN-DIV-16).

TITAN-DIV-18

Filters are a header row in design and a separate region in code.
The exclusivity is the part worth keeping. Whatever happens to the API, the design’s claim is that a page does not get both a tab bar and a header filter row — recorded as TITAN-VIEW-17. The table page template is the worked example: it has tabs in the header, so its filters sit in the body with the data they filter.

TITAN-DIV-19

The selection bar and its select-all menu are designed and absent from code.
This answers a question the view tier had left open. List view recorded three plausible homes for bulk actions — the page header, Controls, or a bar that appears on selection. The design library answers: a bar that appears on selection, in the table’s own toolbar band.

TITAN-DIV-20

A hovered row and a selected row are given the same fill.
The cost lands exactly where selection matters. On a table with bulk actions, the reader answering “which rows did I select” is holding a pointer over the table while they do it, which is the one condition under which the answer is unavailable.

TITAN-DIV-21

Only the standard Alert variant is colour-complete; filled and outlined fail contrast. Computed from the shipped token values, formula validated against published reference ratios:
This is a live conformance failure, not a preference. A filled error alert renders at 2.17 : 1. The cause is structural rather than a bad colour choice: the -alt family is designed as dark text on a pale tint, and filled keeps the dark text while replacing the pale tint with a saturated one. outlined has the mirror problem — it uses the pale background tint as a border colour, where 3 : 1 against white is needed to read as a boundary at all.

TITAN-DIV-22

EmptyState renders its supporting line as a heading.
The visual size is already independent of the level. The title is h3 carrying a bold body type, so the heading level is doing no visual work — which means correcting it costs nothing on screen and fixes the outline for every page that uses the component.

TITAN-DIV-23

The theme’s naive every-word capitalization doesn’t implement title case’s function-word exceptions.

TITAN-DIV-24

Input’s error focus ring’s light-mode value exactly matches border-error’s dark-mode value.

TITAN-DIV-25

The package’s Link export is not the design vocabulary’s Link.

TITAN-DIV-26

The package’s export for FileUpload is named Dropzone.

TITAN-DIV-27

The package’s Dialog export is not the design vocabulary’s Dialog.

TITAN-DIV-28

The package’s export for Panel is named Tile.

TITAN-DIV-29

Popover ships with no Titan-specific decision recorded anywhere.

TITAN-DIV-30

List’s disableBorder, disableHover, and bulleted props render no visual change.

TITAN-DIV-31

Avatar ships with zero Titan decisions.

TITAN-DIV-32

Code ships a bare AppBar; design’s page template depicts a composed site header.

TITAN-DIV-33

Design intent names “Banner” as a product-wide message; no such component ships.

Resolved

Nothing yet. When a divergence is closed, its entry moves here with a note on how it was resolved and in which release.
Last modified on September 7, 2026