Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/danger-destructive-alias.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@tailor-platform/app-shell": minor
---

`--destructive` and `--destructive-foreground` are now aliases of `--danger-solid` and `--danger-contrast`, and the shipped components read the danger roles instead of `destructive` utilities. The names, `Button variant="destructive"` and `text-destructive` keep working.

Visible changes in the default palette:

- Dark-mode `Button variant="destructive"` is a full `#dc2626` fill with white text. It was `#f87171` at 60% opacity. Hover uses `--danger-solid-hover`.
- `text-destructive` now resolves to the solid colour: `#dc2626` in dark mode (was `#f87171`), which is below 4.5:1 as text on the dark card. Error text in components uses `text-danger-text` (light `#ce0c17`, dark `#ff9083`). Use `text-danger-text` for text in your own code.
- Invalid borders and focus rings on `Input`, `Textarea`, `Checkbox`, `Field` and `DateField` read `--danger-solid`. The dark invalid border changes from `#f87171` to `#dc2626` (3.71:1 on the card).
- `CsvImporter` error cells use `bg-danger-surface`, matching the warning cells.
- A theme that overrides `--destructive` no longer changes the shipped components; override `--danger-solid` (and the other danger roles) instead. `cream`, `bloom` and the theme template no longer set `--destructive`; their values were the same as the default.
14 changes: 13 additions & 1 deletion decisions/semantic-color-roles.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ This step is the status part of a two-axis palette. The rest is planned as follo
| ------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ |
| Status (this step) | `--primitive-{blue,green,amber,red}-*` | `--{info,success,warning,danger}-{role}` | done |
| Ink and lines | `--primitive-gray-*` | base tokens read it (`--foreground`, `--muted-foreground`, `--border`, `--input`) | done for default; themes later |
| Danger action | red | `--destructive` becomes an alias of `--danger-solid` once its text and border uses move to `--danger-text` / `--danger-border` | next |
| Danger action | red | `--destructive` becomes an alias of `--danger-solid` once its text and border uses move to `--danger-text` / `--danger-border` | done (2026-10-06) |
| Brand | an accent scale per theme (bloom and cream read blue, theme-tailor reads cyan) | `--primary`, `--ring` and brand tints read accent steps | with the Theme Generator |

`--status-*` and `--alert-*` stay as aliases for consumer code and are deprecated: the docs and changeset say so, and they are removed in the next major. Shipped components read the roles. An ESLint rule in `@tailor-platform/eslint-plugin-app-shell` that flags the old utilities and offers the role as a fix is a separate change.
Expand All @@ -108,3 +108,15 @@ The component variant names (`Badge` and `Alert` `error`, Badge `neutral`) are p
- Alert description text is the same colour as the Alert title in the default palette.
- Consumers who override `--status-*` or `--alert-*` keep working for their own `bg-status-*` and `bg-alert-*` utilities, which read those tokens. The shipped components (Badge, Alert, CsvImporter, MetricCard — its trend colours were literal `green-600` / `red-600` before) read the roles, not `--status-*` or `--alert-*`. To recolour them, override the roles (`--{intent}-*`); overriding `--status-*` or `--alert-*` no longer changes the components. `--status-*` and `--alert-*` remain for existing consumer code and are not used by components any more.
- cream, bloom and `_template.css` values are not edited. The header comments now list `--{intent}-{role}` among the inherited tokens.

## Update 2026-10-06: danger action

The "Danger action" row of the target shape is done. `--destructive` and `--destructive-foreground` are aliases of `--danger-solid` and `--danger-contrast` in `default.css`, light and dark. This supersedes the first item under "Out of scope" and the Consequences bullet that says Button and `text-destructive` still follow `--destructive`.

- Names stay. `Button variant="destructive"`, `text-destructive`, `bg-destructive` and `border-destructive` keep working and are not deprecated: they are the shadcn vocabulary consumers write. `text-danger-text` is the role to prefer for text.
- Shipped components read the danger roles directly: text uses `text-danger-text`; invalid borders use `border-danger-solid`; focus rings use `ring-danger-solid/20` (dark `/40`, toolbar `/30`); tints use `bg-danger-surface`; the static error box border uses `border-danger-border`. Nothing in `packages/core/src` uses a `destructive` utility.
- Invalid inputs keep the solid. `border-danger-border` (step a6, 1.67:1 on the card) and step 8 (2.62:1 in light mode) are below the 3:1 non-text minimum. The solid is 4.83:1 in light mode and 3.71:1 in dark mode on the card. A unit test holds the solid at 3:1 or more over `--card` and `--background`.
- Dark `Button variant="destructive"` is a full `#dc2626` fill (it was `#f87171` at 60% opacity). The white text on `#dc2626` is 4.83:1; hover is `--danger-solid-hover` (5.66:1). Radix and Primer use the mode's step 9 at full; shadcn uses `dark:bg-destructive/60`.
- `cream`, `bloom` and `_template.css` no longer set `--destructive` (their values equalled the default). `_template.css` lists it under DO NOT SET. A theme that recolours destructive actions overrides `--danger-solid`.
- Visible changes in the default palette: light `text-destructive` goes from `#dc2626` to `#ce0c17` through `text-danger-text`, and dark from `#f87171` to `#ff9083`; the dark invalid border goes from `#f87171` to `#dc2626`; CsvImporter error cells use `bg-danger-surface`, matching the warning cells (`bg-warning-surface`).
- Outside this repository: `cp-apps/apps/couturier/src/theme-tailor.css` still overrides `--destructive`, and the Theme Generator still emits `destructive` as a constant. Both stop reaching the shipped components until they move to `--danger-*`.
26 changes: 13 additions & 13 deletions docs-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"hashes": {
"typeSurface": "7cb8a34c1c36eb02",
"outline": "797e012e34847b80",
"snapshot": "791be2c6087af41c",
"snapshot": "5b69cb9a28bbcc2e",
"outputMd": "824d1aa36bb7f194",
"examples": null
}
Expand Down Expand Up @@ -225,7 +225,7 @@
"hashes": {
"typeSurface": "16df9927f8394548",
"outline": "200f12eb4bec959a",
"snapshot": "d607a315ffce3abc",
"snapshot": "985de4f9c817a3cf",
"outputMd": "bce0e2fada1d1638",
"examples": "1ee01b1e27764cd2"
}
Expand Down Expand Up @@ -259,7 +259,7 @@
"hashes": {
"typeSurface": "3886bf7012f24283",
"outline": "916645fe8e5baf10",
"snapshot": "5df639bf40a2bdd7",
"snapshot": "2d057b30b9401e12",
"outputMd": "14f7ba23f441df38",
"examples": "e8f09fba5acb116f"
}
Expand Down Expand Up @@ -676,7 +676,7 @@
"hashes": {
"typeSurface": "7b374d45d53d4535",
"outline": "79c9b0f08ca38027",
"snapshot": "4287a03df2854f9a",
"snapshot": "d22d9caf48bb1b3b",
"outputMd": "91d901b17aed7781",
"examples": null
}
Expand Down Expand Up @@ -897,7 +897,7 @@
"hashes": {
"typeSurface": "7c5b64c579f508ee",
"outline": "9b273bc9e509f1fe",
"snapshot": "7e0ed658f518fd9a",
"snapshot": "4742086e97cfbdee",
"outputMd": "a3a6c9679af99d37",
"examples": "fc036f7af21b9dd4"
}
Expand Down Expand Up @@ -1049,9 +1049,9 @@
"symbols": [],
"hashes": {
"typeSurface": null,
"outline": "45dcfe4383a87ea9",
"outline": "8f4b40e6eb2e5381",
"snapshot": null,
"outputMd": "2fa0b6a249d8bd4f",
"outputMd": "80b53c137713bde8",
"examples": null
}
},
Expand Down Expand Up @@ -1287,9 +1287,9 @@
"symbols": [],
"hashes": {
"typeSurface": null,
"outline": "e63d837190e71ff1",
"outline": "ef379deb65bcba3e",
"snapshot": null,
"outputMd": "112b43c697d30369",
"outputMd": "74855a46c0284abc",
"examples": "4d19f4f688e0f0ac"
}
},
Expand Down Expand Up @@ -1339,7 +1339,7 @@
"hashes": {
"typeSurface": "3d9225f7356a29b2",
"outline": "40952524e46c333e",
"snapshot": "40f4b39567ab051b",
"snapshot": "e775f88bb9fc2565",
"outputMd": "c940bc01cd573334",
"examples": "0712ee03ad6ce259"
}
Expand Down Expand Up @@ -1390,7 +1390,7 @@
"hashes": {
"typeSurface": "2d88f0306efcaa77",
"outline": "1257a84ccd91d5c3",
"snapshot": "3b402cab18147ef9",
"snapshot": "c85c431dcd58bc43",
"outputMd": "91d70b140c7bb747",
"examples": null
}
Expand Down Expand Up @@ -1659,7 +1659,7 @@
"packages/core/skills/app-shell-patterns/references/concepts/modules-and-resources.md": "bf15b5460f22d165",
"packages/core/skills/app-shell-patterns/references/concepts/routing-navigation.md": "8034c7f885994b29",
"packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "dc6b3afc049b4148",
"packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "112b43c697d30369",
"packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "74855a46c0284abc",
"packages/core/skills/app-shell-patterns/references/components/action-panel.md": "71794d4b561e4d4b",
"packages/core/skills/app-shell-patterns/references/components/activity-card.md": "4f386e49380fb0b0",
"packages/core/skills/app-shell-patterns/references/components/ai-chat.md": "824d1aa36bb7f194",
Expand Down Expand Up @@ -1738,7 +1738,7 @@
"packages/core/skills/app-shell-patterns/references/patterns/interaction-toast.md": "f68bdab855730f48",
"packages/core/skills/app-shell-patterns/references/patterns/list-dense-scan.md": "916baa3cd2b9cbf1",
"packages/core/skills/app-shell-patterns/references/pages/document-detail.md": "717ef4e7ae835aff",
"packages/core/skills/app-shell-patterns/references/migrations.md": "013c3fef2303b096",
"packages/core/skills/app-shell-patterns/references/migrations.md": "d5ca48664fedae18",
"packages/core/skills/app-shell-patterns/SKILL.md": "12527965a47cf42f"
}
}
8 changes: 5 additions & 3 deletions docs-src/concepts/styling-theming.docs.outline.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ Surfaces are named by **role**, not by depth — there is no numbered `surface-1
| `--primary` / `--primary-foreground` | primary buttons, emphasis | `bg-primary` / `text-primary-foreground` |
| `--secondary` / `--secondary-foreground` | secondary buttons, neutral chips | `bg-secondary` / `text-secondary-foreground` |
| `--accent` / `--accent-foreground` | hover and selected nav states | `bg-accent` / `text-accent-foreground` |
| `--destructive` / `--destructive-foreground` | destructive actions, errors | `bg-destructive` / `text-destructive` |
| `--destructive` / `--destructive-foreground` | alias of danger solid / contrast | `bg-destructive` / `text-destructive` |

`--destructive` and `--destructive-foreground` are aliases of the danger `solid` and `contrast` roles ([semantic color roles](#semantic-color-roles)), so `bg-destructive` is the danger solid fill in both modes and `Button variant="destructive"` reads the danger roles directly. `text-destructive` still works and resolves to the solid. For text, prefer `text-danger-text`, which meets 4.5:1 on `--card` and `--background`; the solid is `#dc2626` in dark mode, 3.71:1 on the dark card. To recolour destructive actions, override `--danger-solid` (and the other danger roles), not `--destructive`.

There are no `-hover` or `-active` brand tokens. Express interaction states with Tailwind variants and opacity — `hover:bg-primary/90`, `active:bg-primary/80` — which is what AppShell's own components do.

Expand Down Expand Up @@ -195,7 +197,7 @@ In the default palette, `text` on `surface` and `contrast` on `solid` and `solid

Most `surface` and `border` values are translucent tints, so an opacity modifier compounds rather than replaces — `bg-info-surface/50` halves the tint's alpha instead of setting it to 50%. Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent.

`Badge`, `Alert`, `CsvImporter` and `MetricCard` read these roles directly, not `--status-*` or `--alert-*`. To recolour them, override the roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`.
`Badge`, `Alert`, `CsvImporter` and `MetricCard` read these roles directly, not `--status-*` or `--alert-*`. To recolour them, override the roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. `Button variant="destructive"`, invalid input borders and error text follow `--danger-*` too, and `--destructive` is an alias of `--danger-solid`.

#### Sidebar & charts

Expand Down Expand Up @@ -654,7 +656,7 @@ These are visual-composition rules every screen must follow, regardless of patte

| Intent | Pick |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Destructive action (delete, void) | `Button variant="destructive"`; `bg-destructive` on custom surfaces; confirm in a dialog at `shadow-lg` |
| Destructive action (delete, void) | `Button variant="destructive"`; `bg-danger-solid` on custom surface; confirm in a dialog at `shadow-lg` |
| Non-blocking caution | `Badge variant="warning"`, or `bg-warning-surface text-warning-text` |
| Confirmation / completed state | `Badge variant="success"`, or `bg-success-surface text-success-text` |
| Informational callout | `Badge variant="info"`, or `Alert variant="info"` |
Expand Down
21 changes: 21 additions & 0 deletions docs-src/guides/migrations.docs.outline.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Each entry states which versions are affected, what breaks, how to detect it, an

| Version | Change |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| 1.18.0 | [Destructive colours now resolve to the danger roles](#1180-destructive-colours-now-resolve-to-the-danger-roles) |
| 1.17.0 | [Status and alert tokens now resolve to the colour roles](#1170-status-and-alert-tokens-now-resolve-to-the-colour-roles) |
| 1.15.0 | [Base UI 1.8.0 breaks closed-`Select` and dialog-click tests](#1150-base-ui-180-breaks-closed-select-and-dialog-click-tests) |
| 1.12.0 | [`DateField` / `DatePicker` field chrome moved to `Field.Root`](#1120-datefield--datepicker-field-chrome-moved-to-fieldroot) |
Expand All @@ -27,6 +28,26 @@ Each entry states which versions are affected, what breaks, how to detect it, an
| 1.0.2 | [`Toaster` no longer accepts `richColors`](#102-toaster-no-longer-accepts-richcolors) |
| before 1.0 | [Pre-1.0 breaking changes](#before-10) |

## 1.18.0: destructive colours now resolve to the danger roles

**Applies to:** apps that show `Button variant="destructive"` in dark mode, apps that use `text-destructive`, `border-destructive` or `bg-destructive` on their own elements, and apps whose theme overrides `--destructive` or `--destructive-foreground`.

1.18.0 makes `--destructive` and `--destructive-foreground` aliases of `--danger-solid` and `--danger-contrast`. The names, `Button variant="destructive"` and `text-destructive` keep working and are not deprecated. Nothing errors. Three things change silently:

- **Dark-mode `Button variant="destructive"` is a full `#dc2626` fill.** It was `#f87171` at 60% opacity. The text is `--danger-contrast` (white) and the hover fill is `--danger-solid-hover` instead of 90% opacity. Light mode keeps `#dc2626`.
- **`text-destructive` resolves to the solid.** In dark mode it changes from `#f87171` to `#dc2626`, which is 3.71:1 on the dark card, below 4.5:1 for text. Components shipped with AppShell now use `text-danger-text` for error text, and invalid borders and focus rings read `--danger-solid`. In dark mode the invalid border changes from `#f87171` to `#dc2626`.
- **Themes that override `--destructive` no longer reach the components.** `Button`, invalid `Input`, `Textarea`, `Checkbox`, `Field` and `DateField` borders and rings, error text and the `CsvImporter` error cells read `--danger-*`. An override of `--destructive` still changes `bg-destructive` and `text-destructive` on your own elements, so the two reds can drift apart. The `cream` and `bloom` palettes no longer set `--destructive`; their values were the same as the default.

How to detect it: search the app for `destructive` in class names and in theme CSS, including Theme Generator output that sets `--destructive`. Compare `Button variant="destructive"`, an invalid `Input` and an error message in light and dark mode after upgrading.

What to change:

- For text, replace `text-destructive` with `text-danger-text`. `text-destructive` still works; use it only where the solid colour is intended.
- For tints and borders, use `bg-danger-surface` and `border-danger-border` instead of `bg-destructive/10` and `border-destructive/30`.
- In a theme that recolours destructive actions, set `--danger-solid` (and `--danger-solid-hover`, `--danger-text`, `--danger-contrast` as needed) instead of `--destructive`. Remove `--destructive` and `--destructive-foreground` from the theme.

The roles are documented in [Styling & theming](./concepts/styling-theming.md#brand--action). The decision record is `decisions/semantic-color-roles.md`.

## 1.17.0: status and alert tokens now resolve to the colour roles

**Applies to:** apps that use `bg-status-*`, `text-status-*` or `bg-alert-*` utilities, and apps whose theme overrides `--status-*`, `--alert-*` or `--destructive` to recolour `Badge`, `Alert`, `CsvImporter` or `MetricCard`.
Expand Down
Loading
Loading