From 8028ed1905fdb9e5e65b7af0522e8a363abb71ad Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Tue, 6 Oct 2026 20:47:14 +0900 Subject: [PATCH 01/11] feat(core): alias --destructive to the danger solid role Co-Authored-By: Claude Fable 5.1 --- packages/core/src/assets/themes/default.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/assets/themes/default.css b/packages/core/src/assets/themes/default.css index 849f012b5..0f90e6c8a 100644 --- a/packages/core/src/assets/themes/default.css +++ b/packages/core/src/assets/themes/default.css @@ -41,8 +41,8 @@ --sidebar-border: rgba(229, 229, 229, 1); /* ── 5. PALETTE ───────────────────────────────────────────────────────── */ - --destructive: rgba(220, 38, 38, 1); - --destructive-foreground: rgba(254, 242, 242, 1); + --destructive: var(--danger-solid); + --destructive-foreground: var(--danger-contrast); --radius: 0.625rem; --chart-1: rgba(234, 88, 12, 1); --chart-2: rgba(13, 148, 136, 1); @@ -87,8 +87,8 @@ --sidebar-border: rgba(255, 255, 255, 0.1); /* ── 5. PALETTE (dark) ────────────────────────────────────────────────── */ - --destructive: rgba(248, 113, 113, 1); - --destructive-foreground: rgba(254, 242, 242, 1); + --destructive: var(--danger-solid); + --destructive-foreground: var(--danger-contrast); --chart-1: rgba(29, 78, 216, 1); --chart-2: rgba(16, 185, 129, 1); --chart-3: rgba(245, 158, 11, 1); From 1fab9852607f28a893cfabcc9c5e7a76c9cc8088 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Tue, 6 Oct 2026 20:47:32 +0900 Subject: [PATCH 02/11] feat(core): Button destructive uses the danger roles Co-Authored-By: Claude Fable 5.1 --- packages/core/src/components/button/button.test.tsx | 2 +- packages/core/src/components/button/button.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/button/button.test.tsx b/packages/core/src/components/button/button.test.tsx index ec37bdd68..d8afb2d24 100644 --- a/packages/core/src/components/button/button.test.tsx +++ b/packages/core/src/components/button/button.test.tsx @@ -103,7 +103,7 @@ describe("Button", () => { expect(screen.getByRole("button").className).toContain("bg-primary"); rerender(); - expect(screen.getByRole("button").className).toContain("bg-destructive"); + expect(screen.getByRole("button").className).toContain("bg-danger-solid"); rerender(); expect(screen.getByRole("button").className).toContain("border"); diff --git a/packages/core/src/components/button/button.tsx b/packages/core/src/components/button/button.tsx index a99167cb7..a4d5773fc 100644 --- a/packages/core/src/components/button/button.tsx +++ b/packages/core/src/components/button/button.tsx @@ -6,7 +6,7 @@ import { cn } from "@/lib/utils"; import { mergeRefs, useToolbarItem } from "../toolbar/toolbar"; const buttonVariants = cva( - "astw:inline-flex astw:cursor-pointer astw:items-center astw:justify-center astw:gap-2 astw:whitespace-nowrap astw:rounded-md astw:text-sm astw:font-medium astw:transition-all astw:disabled:pointer-events-none astw:disabled:opacity-50 astw:[&_svg]:pointer-events-none astw:[&_svg:not([class*='size-'])]:size-4 astw:shrink-0 astw:[&_svg]:shrink-0 astw:outline-none astw:focus-visible:border-ring astw:focus-visible:ring-ring/50 astw:focus-visible:ring-[3px] astw:aria-invalid:ring-destructive/20 astw:dark:aria-invalid:ring-destructive/40 astw:aria-invalid:border-destructive", + "astw:inline-flex astw:cursor-pointer astw:items-center astw:justify-center astw:gap-2 astw:whitespace-nowrap astw:rounded-md astw:text-sm astw:font-medium astw:transition-all astw:disabled:pointer-events-none astw:disabled:opacity-50 astw:[&_svg]:pointer-events-none astw:[&_svg:not([class*='size-'])]:size-4 astw:shrink-0 astw:[&_svg]:shrink-0 astw:outline-none astw:focus-visible:border-ring astw:focus-visible:ring-ring/50 astw:focus-visible:ring-[3px] astw:aria-invalid:ring-danger-solid/20 astw:dark:aria-invalid:ring-danger-solid/40 astw:aria-invalid:border-danger-solid", { variants: { /** Visual style variant */ @@ -14,7 +14,7 @@ const buttonVariants = cva( default: "astw:bg-primary astw:text-primary-foreground astw:shadow-xs astw:hover:bg-primary/90", destructive: - "astw:bg-destructive astw:text-white astw:shadow-xs astw:hover:bg-destructive/90 astw:focus-visible:ring-destructive/20 astw:dark:focus-visible:ring-destructive/40 astw:dark:bg-destructive/60", + "astw:bg-danger-solid astw:text-danger-contrast astw:shadow-xs astw:hover:bg-danger-solid-hover astw:focus-visible:ring-danger-solid/20 astw:dark:focus-visible:ring-danger-solid/40", outline: "astw:border astw:border-border astw:bg-background astw:shadow-xs astw:hover:bg-accent astw:hover:text-accent-foreground astw:dark:bg-input/30 astw:dark:border-input astw:dark:hover:bg-input/50", secondary: From 4d9143818ae8c3ab0ec0fa925da75f1fa4278156 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Tue, 6 Oct 2026 20:47:47 +0900 Subject: [PATCH 03/11] refactor(core): text-destructive utilities use text-danger-text Co-Authored-By: Claude Fable 5.1 --- .../core/src/components/action-panel/ActionPanel.tsx | 2 +- packages/core/src/components/ai-chat/chat-history.tsx | 2 +- packages/core/src/components/ai-chat/tool.tsx | 4 ++-- packages/core/src/components/appshell/appshell.tsx | 2 +- .../core/src/components/csv-importer/CsvImporter.tsx | 10 +++++----- packages/core/src/components/data-table/data-table.tsx | 4 ++-- packages/core/src/components/data-table/toolbar.tsx | 6 +++--- packages/core/src/components/field/field.tsx | 4 ++-- .../default-error-boundary/default-error-boundary.tsx | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/core/src/components/action-panel/ActionPanel.tsx b/packages/core/src/components/action-panel/ActionPanel.tsx index 168c94a2f..1b8a98b8e 100644 --- a/packages/core/src/components/action-panel/ActionPanel.tsx +++ b/packages/core/src/components/action-panel/ActionPanel.tsx @@ -20,7 +20,7 @@ const actionRowInteractiveClasses = "astw:hover:bg-accent astw:hover:text-accent-foreground astw:outline-none astw:focus-visible:ring-2 astw:focus-visible:ring-ring astw:focus-visible:ring-offset-2"; const actionRowDestructiveClasses = - "astw:text-destructive astw:hover:bg-destructive/10 astw:hover:text-destructive"; + "astw:text-danger-text astw:hover:bg-destructive/10 astw:hover:text-danger-text"; const actionRowDisabledClasses = "astw:pointer-events-none astw:opacity-50"; diff --git a/packages/core/src/components/ai-chat/chat-history.tsx b/packages/core/src/components/ai-chat/chat-history.tsx index 712afaab5..2022f5223 100644 --- a/packages/core/src/components/ai-chat/chat-history.tsx +++ b/packages/core/src/components/ai-chat/chat-history.tsx @@ -83,7 +83,7 @@ function ChatHistory({ // Tucked away visually but never removed from the tab // order or the a11y tree — `hidden` would make // `onDelete` mouse-only. Same pattern as `Attachment`. - className="astw:mr-1 astw:shrink-0 astw:rounded astw:p-1 astw:text-muted-foreground astw:opacity-0 astw:transition-opacity astw:hover:text-destructive astw:group-hover:opacity-100 astw:group-focus-within:opacity-100" + className="astw:mr-1 astw:shrink-0 astw:rounded astw:p-1 astw:text-muted-foreground astw:opacity-0 astw:transition-opacity astw:hover:text-danger-text astw:group-hover:opacity-100 astw:group-focus-within:opacity-100" > diff --git a/packages/core/src/components/ai-chat/tool.tsx b/packages/core/src/components/ai-chat/tool.tsx index 667c9fd4d..c97160945 100644 --- a/packages/core/src/components/ai-chat/tool.tsx +++ b/packages/core/src/components/ai-chat/tool.tsx @@ -37,7 +37,7 @@ const STATUS_ICONS: Record = { "input-streaming": , "input-available": , "output-available": , - "output-error": , + "output-error": , }; type ToolProps = Omit, "children"> & { @@ -150,7 +150,7 @@ function ToolOutput({ output, errorText, className, ...props }: ToolOutputProps)

{errorText ? t("aiChatToolError") : t("aiChatToolResult")} diff --git a/packages/core/src/components/appshell/appshell.tsx b/packages/core/src/components/appshell/appshell.tsx index 3ff3449ae..27cf8920a 100644 --- a/packages/core/src/components/appshell/appshell.tsx +++ b/packages/core/src/components/appshell/appshell.tsx @@ -379,7 +379,7 @@ export const AppShellInternal = (props: AppShellInternalProps) => { return (
-

+

Configuration Error

{errorMessage}

diff --git a/packages/core/src/components/csv-importer/CsvImporter.tsx b/packages/core/src/components/csv-importer/CsvImporter.tsx index 3cc9b9253..ff2193f29 100644 --- a/packages/core/src/components/csv-importer/CsvImporter.tsx +++ b/packages/core/src/components/csv-importer/CsvImporter.tsx @@ -156,7 +156,7 @@ function UploadStep({ /> {error && ( -
+
{error}
@@ -242,7 +242,7 @@ function MappingStep({ {isMapped && } {!isMapped && col.required && ( - + )} {!isMapped && !col.required && ( @@ -255,7 +255,7 @@ function MappingStep({ {col.label} {col.required && ( - * + * )} {col.description && ( @@ -362,7 +362,7 @@ function ReviewStep({ >
Total: {rawRows.length} rows - {errorCount > 0 && Errors: {errorCount}} + {errorCount > 0 && Errors: {errorCount}} {warningCount > 0 && ( Warnings: {warningCount} )} @@ -437,7 +437,7 @@ function ReviewStep({ className={cn( "astw:text-xs astw:px-2", issue.level === "error" - ? "astw:text-destructive" + ? "astw:text-danger-text" : "astw:text-warning-text", )} > diff --git a/packages/core/src/components/data-table/data-table.tsx b/packages/core/src/components/data-table/data-table.tsx index 97271cde7..d5df128cb 100644 --- a/packages/core/src/components/data-table/data-table.tsx +++ b/packages/core/src/components/data-table/data-table.tsx @@ -1141,7 +1141,7 @@ function DataTableBody({ className }: { className?: string }) { return ( - + {t("errorPrefix")} {error.message} @@ -1706,7 +1706,7 @@ function RowActionsMenu>({ action.onClick(row); } }} - className={cn(action.variant === "destructive" && "astw:text-destructive")} + className={cn(action.variant === "destructive" && "astw:text-danger-text")} > {action.icon} {action.label} diff --git a/packages/core/src/components/data-table/toolbar.tsx b/packages/core/src/components/data-table/toolbar.tsx index d83455e98..fb2090b30 100644 --- a/packages/core/src/components/data-table/toolbar.tsx +++ b/packages/core/src/components/data-table/toolbar.tsx @@ -703,7 +703,7 @@ function PanelDateRangeInput({ /> )} - {error &&

{error}

} + {error &&

{error}

}
); } @@ -1026,7 +1026,7 @@ function BetweenInputGroup({ className={inputCell} />
- {error &&

{error}

} + {error &&

{error}

}
); } @@ -1980,7 +1980,7 @@ function TemporalFilterEditor({ value={localValueMax} onChange={setLocalValueMax} /> - {betweenError &&

{betweenError}

} + {betweenError &&

{betweenError}

}
) : ( ); @@ -261,7 +261,7 @@ Error.displayName = "Field.Error"; * * {(state) => * state.validity.valid === false && ( - *

+ *

* {state.validity.typeMismatch * ? "Please enter a valid URL (e.g. https://example.com)" * : "This field is required."} diff --git a/packages/core/src/components/internals/default-error-boundary/default-error-boundary.tsx b/packages/core/src/components/internals/default-error-boundary/default-error-boundary.tsx index 9f1d49f4d..aa8c01ba0 100644 --- a/packages/core/src/components/internals/default-error-boundary/default-error-boundary.tsx +++ b/packages/core/src/components/internals/default-error-boundary/default-error-boundary.tsx @@ -17,7 +17,7 @@ export const DefaultErrorBoundary = () => { return (

- +

{t("error404Title")}

{t("error404Body")}

@@ -41,7 +41,7 @@ export const DefaultErrorBoundary = () => { return (
- +

{t("errorTitle")}

{message}

From 46e40198c923f8945e1fb4ad7fae5eed59f9e456 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Tue, 6 Oct 2026 20:48:00 +0900 Subject: [PATCH 04/11] refactor(core): invalid borders and focus rings use the danger roles Co-Authored-By: Claude Fable 5.1 --- packages/core/src/components/appshell/appshell.tsx | 2 +- packages/core/src/components/checkbox/checkbox.test.tsx | 2 +- packages/core/src/components/checkbox/checkbox.tsx | 4 ++-- packages/core/src/components/csv-importer/CsvImporter.tsx | 2 +- packages/core/src/components/data-table/toolbar.tsx | 2 +- packages/core/src/components/date-field/date-input-group.tsx | 2 +- packages/core/src/components/field/field.tsx | 2 +- packages/core/src/components/input/input.tsx | 2 +- packages/core/src/components/textarea/textarea.tsx | 4 ++-- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/core/src/components/appshell/appshell.tsx b/packages/core/src/components/appshell/appshell.tsx index 27cf8920a..775bb8ad8 100644 --- a/packages/core/src/components/appshell/appshell.tsx +++ b/packages/core/src/components/appshell/appshell.tsx @@ -378,7 +378,7 @@ export const AppShellInternal = (props: AppShellInternalProps) => { // Render inline error UI instead of throwing return (
-
+

Configuration Error

diff --git a/packages/core/src/components/checkbox/checkbox.test.tsx b/packages/core/src/components/checkbox/checkbox.test.tsx index 70c8e17ea..1baf29841 100644 --- a/packages/core/src/components/checkbox/checkbox.test.tsx +++ b/packages/core/src/components/checkbox/checkbox.test.tsx @@ -127,7 +127,7 @@ describe("Checkbox", () => { render(); // shadcn's FormControl signals invalid via aria-invalid (no Base UI Field // in play), so the box must key its destructive styling off it too. - expect(screen.getByRole("checkbox").className).toContain("aria-invalid:border-destructive"); + expect(screen.getByRole("checkbox").className).toContain("aria-invalid:border-danger-solid"); }); describe("Field integration", () => { diff --git a/packages/core/src/components/checkbox/checkbox.tsx b/packages/core/src/components/checkbox/checkbox.tsx index d8c00a8eb..b3574ff31 100644 --- a/packages/core/src/components/checkbox/checkbox.tsx +++ b/packages/core/src/components/checkbox/checkbox.tsx @@ -30,8 +30,8 @@ const checkboxBoxClasses = cn( "astw:data-checked:bg-primary astw:data-checked:border-primary astw:data-checked:text-primary-foreground", "astw:data-indeterminate:bg-primary astw:data-indeterminate:border-primary astw:data-indeterminate:text-primary-foreground", "astw:focus-visible:border-ring astw:focus-visible:ring-ring/50 astw:focus-visible:ring-[3px]", - "astw:data-invalid:border-destructive astw:data-invalid:ring-destructive/20 astw:dark:data-invalid:ring-destructive/40", - "astw:aria-invalid:border-destructive astw:aria-invalid:ring-destructive/20 astw:dark:aria-invalid:ring-destructive/40", + "astw:data-invalid:border-danger-solid astw:data-invalid:ring-danger-solid/20 astw:dark:data-invalid:ring-danger-solid/40", + "astw:aria-invalid:border-danger-solid astw:aria-invalid:ring-danger-solid/20 astw:dark:aria-invalid:ring-danger-solid/40", "astw:data-disabled:cursor-not-allowed", ); diff --git a/packages/core/src/components/csv-importer/CsvImporter.tsx b/packages/core/src/components/csv-importer/CsvImporter.tsx index ff2193f29..9cab6cf9c 100644 --- a/packages/core/src/components/csv-importer/CsvImporter.tsx +++ b/packages/core/src/components/csv-importer/CsvImporter.tsx @@ -425,7 +425,7 @@ function ReviewStep({ type="text" className={cn( "astw:w-full astw:rounded astw:border astw:px-2 astw:py-1 astw:text-sm astw:bg-transparent", - issue?.level === "error" && "astw:border-destructive", + issue?.level === "error" && "astw:border-danger-solid", issue?.level === "warning" && "astw:border-warning-solid", !issue && "astw:border-transparent", )} diff --git a/packages/core/src/components/data-table/toolbar.tsx b/packages/core/src/components/data-table/toolbar.tsx index fb2090b30..e43cc56b7 100644 --- a/packages/core/src/components/data-table/toolbar.tsx +++ b/packages/core/src/components/data-table/toolbar.tsx @@ -990,7 +990,7 @@ function BetweenInputGroup({ "astw:flex astw:items-center astw:h-8 astw:rounded-md astw:border astw:shadow-xs astw:has-focus-visible:ring-[3px]"; const rowOk = "astw:border-input astw:has-focus-visible:border-ring astw:has-focus-visible:ring-ring/50"; - const rowError = "astw:border-destructive astw:has-focus-visible:ring-destructive/30"; + const rowError = "astw:border-danger-solid astw:has-focus-visible:ring-danger-solid/30"; const labelCell = "astw:text-secondary-foreground astw:text-xs astw:px-2.5 astw:border-r astw:border-input astw:bg-muted astw:rounded-l-md astw:h-full astw:flex astw:items-center astw:justify-center astw:shrink-0 astw:min-w-14"; const inputCell = diff --git a/packages/core/src/components/date-field/date-input-group.tsx b/packages/core/src/components/date-field/date-input-group.tsx index 8c445dbec..6da6f0acf 100644 --- a/packages/core/src/components/date-field/date-input-group.tsx +++ b/packages/core/src/components/date-field/date-input-group.tsx @@ -35,7 +35,7 @@ export const groupClasses = cn( // Overrides inputBaseClasses' `min-w-0`; wider locales (e.g. ja-JP) still grow. "astw:flex astw:h-9 astw:min-w-[142px] astw:items-center astw:gap-0 astw:py-0", "astw:focus-within:border-ring astw:focus-within:ring-[3px] astw:focus-within:ring-ring/50", - "astw:data-[invalid]:border-destructive astw:data-[invalid]:ring-destructive/20", + "astw:data-[invalid]:border-danger-solid astw:data-[invalid]:ring-danger-solid/20", "astw:data-[disabled]:cursor-not-allowed astw:data-[disabled]:opacity-50", ); diff --git a/packages/core/src/components/field/field.tsx b/packages/core/src/components/field/field.tsx index adec256b9..1f7c29a5e 100644 --- a/packages/core/src/components/field/field.tsx +++ b/packages/core/src/components/field/field.tsx @@ -140,7 +140,7 @@ function Control({ className, ...props }: React.ComponentProps` tag, so From 2c8b84e8ae0e4671b561eee1bd26c3c41426b7ec Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Tue, 6 Oct 2026 20:48:03 +0900 Subject: [PATCH 05/11] refactor(core): destructive tints use bg-danger-surface Co-Authored-By: Claude Fable 5.1 --- packages/core/src/components/action-panel/ActionPanel.tsx | 2 +- packages/core/src/components/appshell/appshell.tsx | 2 +- packages/core/src/components/csv-importer/CsvImporter.tsx | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/action-panel/ActionPanel.tsx b/packages/core/src/components/action-panel/ActionPanel.tsx index 1b8a98b8e..1883f6b57 100644 --- a/packages/core/src/components/action-panel/ActionPanel.tsx +++ b/packages/core/src/components/action-panel/ActionPanel.tsx @@ -20,7 +20,7 @@ const actionRowInteractiveClasses = "astw:hover:bg-accent astw:hover:text-accent-foreground astw:outline-none astw:focus-visible:ring-2 astw:focus-visible:ring-ring astw:focus-visible:ring-offset-2"; const actionRowDestructiveClasses = - "astw:text-danger-text astw:hover:bg-destructive/10 astw:hover:text-danger-text"; + "astw:text-danger-text astw:hover:bg-danger-surface astw:hover:text-danger-text"; const actionRowDisabledClasses = "astw:pointer-events-none astw:opacity-50"; diff --git a/packages/core/src/components/appshell/appshell.tsx b/packages/core/src/components/appshell/appshell.tsx index 775bb8ad8..96e750038 100644 --- a/packages/core/src/components/appshell/appshell.tsx +++ b/packages/core/src/components/appshell/appshell.tsx @@ -378,7 +378,7 @@ export const AppShellInternal = (props: AppShellInternalProps) => { // Render inline error UI instead of throwing return (
-
+

Configuration Error

diff --git a/packages/core/src/components/csv-importer/CsvImporter.tsx b/packages/core/src/components/csv-importer/CsvImporter.tsx index 9cab6cf9c..236270258 100644 --- a/packages/core/src/components/csv-importer/CsvImporter.tsx +++ b/packages/core/src/components/csv-importer/CsvImporter.tsx @@ -156,7 +156,7 @@ function UploadStep({ /> {error && ( -
+
{error}
@@ -235,7 +235,7 @@ function MappingStep({ key={col.key} className={cn( "astw:border-b astw:border-border astw:last:border-b-0 astw:transition-colors", - !isMapped && col.required && "astw:bg-destructive/5", + !isMapped && col.required && "astw:bg-danger-surface", )} > {/* Status icon */} @@ -416,7 +416,7 @@ function ReviewStep({ key={m.csvHeader} className={cn( "astw:px-1 astw:py-1", - issue?.level === "error" && "astw:bg-destructive/10", + issue?.level === "error" && "astw:bg-danger-surface", issue?.level === "warning" && "astw:bg-warning-surface", )} > From 0665ba7c77841223c6ea963b83ef3fc659f2d302 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Tue, 6 Oct 2026 20:48:13 +0900 Subject: [PATCH 06/11] refactor(core): themes inherit --destructive from the danger roles Co-Authored-By: Claude Fable 5.1 --- packages/core/src/assets/theme.css | 5 +++-- packages/core/src/assets/themes/_template.css | 8 ++++---- packages/core/src/assets/themes/bloom.css | 2 -- packages/core/src/assets/themes/cream.css | 2 -- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/core/src/assets/theme.css b/packages/core/src/assets/theme.css index 97899dee8..e22dcd1a0 100644 --- a/packages/core/src/assets/theme.css +++ b/packages/core/src/assets/theme.css @@ -13,10 +13,11 @@ * 2. SHELL GRADIENT — optional; branded palettes only (bloom, cream). * 3. SYSTEM — surfaces & chrome; copy default or tune for brand tint * (foreground, muted, popover, card, borders). - * 4. PALETTE — optional: radius, charts, shadows, destructive. + * 4. PALETTE — optional: radius, charts, shadows. * * Inherited from default.css — do not duplicate in new palettes: - * --ring, --sidebar-*, --primitive-* (colour scales), --status-*, --alert-*. + * --ring, --sidebar-*, --primitive-* (colour scales), --status-*, --alert-*, + * --destructive, --destructive-foreground (aliases of the danger roles). * The colour roles --{intent}-{role} inherit too; a palette overrides them * only to recolour the status components. * diff --git a/packages/core/src/assets/themes/_template.css b/packages/core/src/assets/themes/_template.css index 6783295fa..882c0e51c 100644 --- a/packages/core/src/assets/themes/_template.css +++ b/packages/core/src/assets/themes/_template.css @@ -17,10 +17,12 @@ * `@tailor-platform/app-shell/styles` in the consuming app. * * DO NOT SET (inherited from `:root` in default.css): - * --ring, --sidebar-*, --primitive-* (colour scales), --status-*, --alert-* + * --ring, --sidebar-*, --primitive-* (colour scales), --status-*, --alert-*, + * --destructive, --destructive-foreground * The colour roles --{info|success|warning|danger|neutral}-{role} also inherit. * Override a role only to recolour the status components (Badge, Alert, ...) - * for this palette; never set --status-* or --alert-*, they alias the roles. + * for this palette; never set --status-*, --alert-* or --destructive*, they alias the roles + * (--destructive is the danger solid; recolour it with --danger-solid). * * PREVIEW: `/showcase/colors` in the Vite example app. * ═══════════════════════════════════════════════════════════════════════════ @@ -57,7 +59,6 @@ --sidebar-border: rgba(229, 229, 229, 1); /* ── 4. PALETTE — optional overrides (else inherited from :root) ──────── */ - --destructive: rgba(220, 38, 38, 1); --radius: 0.625rem; --chart-1: /* brand-adjacent */; --chart-2: /* ... */; @@ -101,7 +102,6 @@ --sidebar-border: rgba(255, 255, 255, 0.1); /* ── 4. PALETTE (dark overrides) ─────────────────────────────────────── */ - --destructive: rgba(248, 113, 113, 1); --chart-1: /* ... */; --chart-2: /* ... */; --chart-3: /* ... */; diff --git a/packages/core/src/assets/themes/bloom.css b/packages/core/src/assets/themes/bloom.css index 2835473a0..6494250e0 100644 --- a/packages/core/src/assets/themes/bloom.css +++ b/packages/core/src/assets/themes/bloom.css @@ -34,7 +34,6 @@ --sidebar-border: rgba(0, 0, 0, 0.08); /* ── 4. PALETTE ───────────────────────────────────────────────────────── */ - --destructive: rgba(220, 38, 38, 1); --radius: 1rem; --chart-1: rgba(83, 90, 232, 1); --chart-2: rgba(0, 151, 156, 1); @@ -78,7 +77,6 @@ --sidebar-border: rgba(255, 255, 255, 0.08); /* ── 4. PALETTE (dark) ────────────────────────────────────────────────── */ - --destructive: rgba(248, 113, 113, 1); --chart-1: rgba(131, 138, 247, 1); --chart-2: rgba(45, 191, 196, 1); --chart-3: rgba(94, 234, 212, 1); diff --git a/packages/core/src/assets/themes/cream.css b/packages/core/src/assets/themes/cream.css index 3d5828707..07d93cd6f 100644 --- a/packages/core/src/assets/themes/cream.css +++ b/packages/core/src/assets/themes/cream.css @@ -34,7 +34,6 @@ --sidebar-border: rgba(0, 0, 0, 0.08); /* ── 4. PALETTE ───────────────────────────────────────────────────────── */ - --destructive: rgba(220, 38, 38, 1); --radius: 1rem; --chart-1: rgba(83, 90, 232, 1); --chart-2: rgba(0, 151, 156, 1); @@ -78,7 +77,6 @@ --sidebar-border: rgba(255, 255, 255, 0.08); /* ── 4. PALETTE (dark) ────────────────────────────────────────────────── */ - --destructive: rgba(248, 113, 113, 1); --chart-1: rgba(131, 138, 247, 1); --chart-2: rgba(45, 191, 196, 1); --chart-3: rgba(94, 234, 212, 1); From 44c6c9a60f138dd90de1e5c14e4f364cdbceacb4 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Tue, 6 Oct 2026 20:50:00 +0900 Subject: [PATCH 07/11] test(core): cover destructive aliases and Button danger utilities, update snapshots Co-Authored-By: Claude Fable 5.1 --- ...components__ai-chat__ai-chat.test.tsx.snap | 4 +-- ...__components__button__button.test.tsx.snap | 24 +++++++------- ...mponents__checkbox__checkbox.test.tsx.snap | 10 +++--- ...ents__date-field__date-field.test.tsx.snap | 6 ++-- ...ate-field__date-range-picker.test.tsx.snap | 2 +- ...rc__components__field__field.test.tsx.snap | 10 +++--- .../src__components__form__form.test.tsx.snap | 4 +-- ...rc__components__input__input.test.tsx.snap | 12 +++---- ...mponents__textarea__textarea.test.tsx.snap | 8 ++--- ...components__toolbar__toolbar.test.tsx.snap | 2 +- .../core/src/assets/semantic-contrast.test.ts | 16 ++++++++++ .../src/components/button/button.test.tsx | 32 +++++++++++++++++++ 12 files changed, 89 insertions(+), 41 deletions(-) diff --git a/packages/core/__snapshots__/src__components__ai-chat__ai-chat.test.tsx.snap b/packages/core/__snapshots__/src__components__ai-chat__ai-chat.test.tsx.snap index bce0f5fed..a6bedcf54 100644 --- a/packages/core/__snapshots__/src__components__ai-chat__ai-chat.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__ai-chat__ai-chat.test.tsx.snap @@ -1,5 +1,5 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`AIChat > snapshots > default 1`] = `"

Hello there.

"`; +exports[`AIChat > snapshots > default 1`] = `"

Hello there.

"`; -exports[`AIChat > snapshots > with header 1`] = `"
Assistant
"`; +exports[`AIChat > snapshots > with header 1`] = `"
Assistant
"`; diff --git a/packages/core/__snapshots__/src__components__button__button.test.tsx.snap b/packages/core/__snapshots__/src__components__button__button.test.tsx.snap index c172b7310..4663cab3e 100644 --- a/packages/core/__snapshots__/src__components__button__button.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__button__button.test.tsx.snap @@ -1,25 +1,25 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Button > snapshots > default variant and size 1`] = `""`; +exports[`Button > snapshots > default variant and size 1`] = `""`; -exports[`Button > snapshots > destructive variant 1`] = `""`; +exports[`Button > snapshots > destructive variant 1`] = `""`; -exports[`Button > snapshots > disabled state 1`] = `""`; +exports[`Button > snapshots > disabled state 1`] = `""`; -exports[`Button > snapshots > extra-small size 1`] = `""`; +exports[`Button > snapshots > extra-small size 1`] = `""`; -exports[`Button > snapshots > ghost variant 1`] = `""`; +exports[`Button > snapshots > ghost variant 1`] = `""`; -exports[`Button > snapshots > icon size 1`] = `""`; +exports[`Button > snapshots > icon size 1`] = `""`; -exports[`Button > snapshots > large size 1`] = `""`; +exports[`Button > snapshots > large size 1`] = `""`; -exports[`Button > snapshots > link variant 1`] = `""`; +exports[`Button > snapshots > link variant 1`] = `""`; -exports[`Button > snapshots > outline variant 1`] = `""`; +exports[`Button > snapshots > outline variant 1`] = `""`; -exports[`Button > snapshots > secondary variant 1`] = `""`; +exports[`Button > snapshots > secondary variant 1`] = `""`; -exports[`Button > snapshots > small size 1`] = `""`; +exports[`Button > snapshots > small size 1`] = `""`; -exports[`Button > snapshots > with render prop (as link) 1`] = `"Link Button"`; +exports[`Button > snapshots > with render prop (as link) 1`] = `"Link Button"`; diff --git a/packages/core/__snapshots__/src__components__checkbox__checkbox.test.tsx.snap b/packages/core/__snapshots__/src__components__checkbox__checkbox.test.tsx.snap index 7c10d2068..e8a585192 100644 --- a/packages/core/__snapshots__/src__components__checkbox__checkbox.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__checkbox__checkbox.test.tsx.snap @@ -1,11 +1,11 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Checkbox > snapshots > checked 1`] = `""`; +exports[`Checkbox > snapshots > checked 1`] = `""`; -exports[`Checkbox > snapshots > disabled 1`] = `""`; +exports[`Checkbox > snapshots > disabled 1`] = `""`; -exports[`Checkbox > snapshots > indeterminate 1`] = `""`; +exports[`Checkbox > snapshots > indeterminate 1`] = `""`; -exports[`Checkbox > snapshots > unchecked 1`] = `""`; +exports[`Checkbox > snapshots > unchecked 1`] = `""`; -exports[`Checkbox > snapshots > with label 1`] = `""`; +exports[`Checkbox > snapshots > with label 1`] = `""`; diff --git a/packages/core/__snapshots__/src__components__date-field__date-field.test.tsx.snap b/packages/core/__snapshots__/src__components__date-field__date-field.test.tsx.snap index ea542ba06..4464f41d5 100644 --- a/packages/core/__snapshots__/src__components__date-field__date-field.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__date-field__date-field.test.tsx.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`snapshots > DateField 1`] = `"
mm
dd
yyyy
"`; +exports[`snapshots > DateField 1`] = `"
mm
dd
yyyy
"`; -exports[`snapshots > DateField — manually labelled + described 1`] = `"
mm
dd
yyyy

Pick a date

Required

"`; +exports[`snapshots > DateField — manually labelled + described 1`] = `"
mm
dd
yyyy

Pick a date

Required

"`; -exports[`snapshots > DatePicker — closed 1`] = `"
mm
dd
yyyy
"`; +exports[`snapshots > DatePicker — closed 1`] = `"
mm
dd
yyyy
"`; diff --git a/packages/core/__snapshots__/src__components__date-field__date-range-picker.test.tsx.snap b/packages/core/__snapshots__/src__components__date-field__date-range-picker.test.tsx.snap index 134c84193..1e0dc0b99 100644 --- a/packages/core/__snapshots__/src__components__date-field__date-range-picker.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__date-field__date-range-picker.test.tsx.snap @@ -1,3 +1,3 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`snapshots > DateRangePicker — closed 1`] = `"
mm
dd
yyyy
mm
dd
yyyy
"`; +exports[`snapshots > DateRangePicker — closed 1`] = `"
mm
dd
yyyy
mm
dd
yyyy
"`; diff --git a/packages/core/__snapshots__/src__components__field__field.test.tsx.snap b/packages/core/__snapshots__/src__components__field__field.test.tsx.snap index d86f84a4d..b00884b4c 100644 --- a/packages/core/__snapshots__/src__components__field__field.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__field__field.test.tsx.snap @@ -1,11 +1,11 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Field > snapshots > basic field with label and control 1`] = `"
"`; +exports[`Field > snapshots > basic field with label and control 1`] = `"
"`; -exports[`Field > snapshots > disabled field 1`] = `"
"`; +exports[`Field > snapshots > disabled field 1`] = `"
"`; -exports[`Field > snapshots > field with custom className 1`] = `"
"`; +exports[`Field > snapshots > field with custom className 1`] = `"
"`; -exports[`Field > snapshots > field with description 1`] = `"

We will never share your email.

"`; +exports[`Field > snapshots > field with description 1`] = `"

We will never share your email.

"`; -exports[`Field > snapshots > field with error 1`] = `"
Please enter a valid URL.
"`; +exports[`Field > snapshots > field with error 1`] = `"
Please enter a valid URL.
"`; diff --git a/packages/core/__snapshots__/src__components__form__form.test.tsx.snap b/packages/core/__snapshots__/src__components__form__form.test.tsx.snap index 50a1a6737..8e650a7be 100644 --- a/packages/core/__snapshots__/src__components__form__form.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__form__form.test.tsx.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Form > snapshots > basic form with a field 1`] = `"
"`; +exports[`Form > snapshots > basic form with a field 1`] = `"
"`; exports[`Form > snapshots > form with custom className 1`] = `"
Content
"`; -exports[`Form > snapshots > form with noValidate 1`] = `"
"`; +exports[`Form > snapshots > form with noValidate 1`] = `"
"`; diff --git a/packages/core/__snapshots__/src__components__input__input.test.tsx.snap b/packages/core/__snapshots__/src__components__input__input.test.tsx.snap index 0f907a1f2..d3ccf7465 100644 --- a/packages/core/__snapshots__/src__components__input__input.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__input__input.test.tsx.snap @@ -1,13 +1,13 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Input > snapshots > default text input 1`] = `""`; +exports[`Input > snapshots > default text input 1`] = `""`; -exports[`Input > snapshots > disabled input 1`] = `""`; +exports[`Input > snapshots > disabled input 1`] = `""`; -exports[`Input > snapshots > email input 1`] = `""`; +exports[`Input > snapshots > email input 1`] = `""`; -exports[`Input > snapshots > file input 1`] = `""`; +exports[`Input > snapshots > file input 1`] = `""`; -exports[`Input > snapshots > password input 1`] = `""`; +exports[`Input > snapshots > password input 1`] = `""`; -exports[`Input > snapshots > with custom className 1`] = `""`; +exports[`Input > snapshots > with custom className 1`] = `""`; diff --git a/packages/core/__snapshots__/src__components__textarea__textarea.test.tsx.snap b/packages/core/__snapshots__/src__components__textarea__textarea.test.tsx.snap index 47cb882d8..b7a1e8b41 100644 --- a/packages/core/__snapshots__/src__components__textarea__textarea.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__textarea__textarea.test.tsx.snap @@ -1,9 +1,9 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Textarea > snapshots > default 1`] = `""`; +exports[`Textarea > snapshots > default 1`] = `""`; -exports[`Textarea > snapshots > disabled 1`] = `""`; +exports[`Textarea > snapshots > disabled 1`] = `""`; -exports[`Textarea > snapshots > with custom className 1`] = `""`; +exports[`Textarea > snapshots > with custom className 1`] = `""`; -exports[`Textarea > snapshots > with rows 1`] = `""`; +exports[`Textarea > snapshots > with rows 1`] = `""`; diff --git a/packages/core/__snapshots__/src__components__toolbar__toolbar.test.tsx.snap b/packages/core/__snapshots__/src__components__toolbar__toolbar.test.tsx.snap index 95bd03781..8534f44c3 100644 --- a/packages/core/__snapshots__/src__components__toolbar__toolbar.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__toolbar__toolbar.test.tsx.snap @@ -1,3 +1,3 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Toolbar > renders grouped controls and a separator 1`] = `"
"`; +exports[`Toolbar > renders grouped controls and a separator 1`] = `"
"`; diff --git a/packages/core/src/assets/semantic-contrast.test.ts b/packages/core/src/assets/semantic-contrast.test.ts index 61974dd16..879a63ee7 100644 --- a/packages/core/src/assets/semantic-contrast.test.ts +++ b/packages/core/src/assets/semantic-contrast.test.ts @@ -88,6 +88,8 @@ function contrast(a: Rgba, b: Rgba): number { } const MIN_TEXT = 4.5; +/** WCAG 1.4.11 non-text contrast, for the invalid-input border. */ +const MIN_NON_TEXT = 3; /** Known and accepted: subtle hover text is below AA for some roles. A floor, not a pass. */ const MIN_HOVER_TEXT = 4.2; const modes: Mode[] = ["light", "dark"]; @@ -151,4 +153,18 @@ describe.each(modes)("semantic colour contrast (%s)", (mode) => { ).toBeGreaterThanOrEqual(MIN_TEXT); }); }); + + describe("destructive aliases", () => { + it.each([ + ["--destructive", "--danger-solid"], + ["--destructive-foreground", "--danger-contrast"], + ])("%s resolves to %s", (alias, role) => { + expect(vars.get(alias)).toBe(`var(${role})`); + expect(color(alias)).toEqual(color(role)); + }); + + it.each(pages)("danger solid meets 3:1 against %s (invalid-input border)", (page) => { + expect(contrast(color("--danger-solid"), color(page))).toBeGreaterThanOrEqual(MIN_NON_TEXT); + }); + }); }); diff --git a/packages/core/src/components/button/button.test.tsx b/packages/core/src/components/button/button.test.tsx index d8afb2d24..d18a4c3b5 100644 --- a/packages/core/src/components/button/button.test.tsx +++ b/packages/core/src/components/button/button.test.tsx @@ -1,3 +1,6 @@ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; import { afterEach, describe, expect, it, vi } from "vitest"; import { cleanup, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; @@ -98,6 +101,35 @@ describe("Button", () => { expect(onClick).toHaveBeenCalled(); }); + it("destructive variant uses only bridged danger utilities", () => { + // Precompiled `astw:` utilities emit no CSS when their token is missing + // from the Tailwind bridge, so check every danger class the variant uses. + const bridge = readFileSync( + join(dirname(fileURLToPath(import.meta.url)), "../../assets/theme.bridge.css"), + "utf8", + ); + render(); + const classes = screen.getByRole("button").className.split(/\s+/); + + expect(classes.some((c) => c.includes("destructive"))).toBe(false); + expect(classes).toContain("astw:bg-danger-solid"); + expect(classes).toContain("astw:text-danger-contrast"); + expect(classes).toContain("astw:hover:bg-danger-solid-hover"); + + const tokens = classes + .map( + (c) => + /^astw:(?:dark:)?(?:hover:|focus-visible:)?(?:bg|text|ring)-(danger-[a-z-]+)/.exec( + c, + )?.[1], + ) + .filter((t): t is string => t !== undefined); + expect(tokens.length).toBeGreaterThanOrEqual(3); + for (const token of tokens) { + expect(bridge).toContain(`--color-${token}:`); + } + }); + it("renders different variants", () => { const { rerender } = render(); expect(screen.getByRole("button").className).toContain("bg-primary"); From 0eb782675ad2fe7200df25e9fdab040a2881c50d Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Tue, 6 Oct 2026 20:52:06 +0900 Subject: [PATCH 08/11] docs: document --destructive as the danger solid alias Co-Authored-By: Claude Fable 5.1 --- decisions/semantic-color-roles.md | 14 +++++++++- docs-manifest.json | 26 +++++++++---------- .../concepts/styling-theming.docs.outline.md | 8 +++--- docs-src/guides/migrations.docs.outline.md | 21 +++++++++++++++ docs/concepts/styling-theming.md | 8 +++--- docs/migrations.md | 21 +++++++++++++++ 6 files changed, 78 insertions(+), 20 deletions(-) diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index 23927ead4..0250fe1b9 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -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. @@ -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-*`. diff --git a/docs-manifest.json b/docs-manifest.json index dbff2066a..79de7d836 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -65,7 +65,7 @@ "hashes": { "typeSurface": "7cb8a34c1c36eb02", "outline": "797e012e34847b80", - "snapshot": "791be2c6087af41c", + "snapshot": "5b69cb9a28bbcc2e", "outputMd": "824d1aa36bb7f194", "examples": null } @@ -225,7 +225,7 @@ "hashes": { "typeSurface": "16df9927f8394548", "outline": "200f12eb4bec959a", - "snapshot": "d607a315ffce3abc", + "snapshot": "985de4f9c817a3cf", "outputMd": "bce0e2fada1d1638", "examples": "1ee01b1e27764cd2" } @@ -259,7 +259,7 @@ "hashes": { "typeSurface": "3886bf7012f24283", "outline": "916645fe8e5baf10", - "snapshot": "5df639bf40a2bdd7", + "snapshot": "2d057b30b9401e12", "outputMd": "14f7ba23f441df38", "examples": "e8f09fba5acb116f" } @@ -673,7 +673,7 @@ "hashes": { "typeSurface": "7b374d45d53d4535", "outline": "79c9b0f08ca38027", - "snapshot": "4287a03df2854f9a", + "snapshot": "d22d9caf48bb1b3b", "outputMd": "91d901b17aed7781", "examples": null } @@ -894,7 +894,7 @@ "hashes": { "typeSurface": "7c5b64c579f508ee", "outline": "9b273bc9e509f1fe", - "snapshot": "7e0ed658f518fd9a", + "snapshot": "4742086e97cfbdee", "outputMd": "a3a6c9679af99d37", "examples": "fc036f7af21b9dd4" } @@ -1046,9 +1046,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "45dcfe4383a87ea9", + "outline": "8f4b40e6eb2e5381", "snapshot": null, - "outputMd": "2fa0b6a249d8bd4f", + "outputMd": "80b53c137713bde8", "examples": null } }, @@ -1284,9 +1284,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "c8a79ca086341d8c", + "outline": "0abe88ee82688f3d", "snapshot": null, - "outputMd": "1277e06bb82432e6", + "outputMd": "be7dd6881d489be5", "examples": "d3cca347b055a503" } }, @@ -1336,7 +1336,7 @@ "hashes": { "typeSurface": "3d9225f7356a29b2", "outline": "40952524e46c333e", - "snapshot": "40f4b39567ab051b", + "snapshot": "e775f88bb9fc2565", "outputMd": "c940bc01cd573334", "examples": "0712ee03ad6ce259" } @@ -1387,7 +1387,7 @@ "hashes": { "typeSurface": "2d88f0306efcaa77", "outline": "1257a84ccd91d5c3", - "snapshot": "3b402cab18147ef9", + "snapshot": "c85c431dcd58bc43", "outputMd": "91d70b140c7bb747", "examples": null } @@ -1656,7 +1656,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": "4becc2090217bfd2", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "dc6b3afc049b4148", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "1277e06bb82432e6", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "be7dd6881d489be5", "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", @@ -1735,7 +1735,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": "8062ea0df9403fbb", "packages/core/skills/app-shell-patterns/references/pages/document-detail.md": "c3fb2588a0699c26", - "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": "814325f4ea06ffae" } } diff --git a/docs-src/concepts/styling-theming.docs.outline.md b/docs-src/concepts/styling-theming.docs.outline.md index dff987ad7..b8f05fc11 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -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. @@ -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 @@ -576,7 +578,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"` | diff --git a/docs-src/guides/migrations.docs.outline.md b/docs-src/guides/migrations.docs.outline.md index cca666b24..47b4b637b 100644 --- a/docs-src/guides/migrations.docs.outline.md +++ b/docs-src/guides/migrations.docs.outline.md @@ -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) | @@ -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`. diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index b553fc46d..78aeee1f1 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -169,7 +169,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. @@ -212,7 +214,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 @@ -593,7 +595,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"` | diff --git a/docs/migrations.md b/docs/migrations.md index 4ffc91542..a0849155f 100644 --- a/docs/migrations.md +++ b/docs/migrations.md @@ -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) | @@ -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`. From 39f68e2dc3fd36a5ec8c23c9bdc5199a207c40fc Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Tue, 6 Oct 2026 20:52:11 +0900 Subject: [PATCH 09/11] chore: add changeset for the destructive danger alias Co-Authored-By: Claude Fable 5.1 --- .changeset/danger-destructive-alias.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/danger-destructive-alias.md diff --git a/.changeset/danger-destructive-alias.md b/.changeset/danger-destructive-alias.md new file mode 100644 index 000000000..fdfae7647 --- /dev/null +++ b/.changeset/danger-destructive-alias.md @@ -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. From 0aa1390b2fb32c26a8e718594777539d2fc888e5 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 14 Oct 2026 09:02:14 +0900 Subject: [PATCH 10/11] docs(core): mark destructive tokens and utilities as deprecated --destructive, --destructive-foreground and their utilities will be removed in the next major (tailor-inc/platform-planning#2084), the same as --status-* and --alert-*. Button variant="destructive" stays undeprecated until a danger variant exists. Co-Authored-By: Claude Opus 5.5 --- .changeset/danger-destructive-alias.md | 2 ++ decisions/semantic-color-roles.md | 2 +- docs-manifest.json | 12 ++++++------ .../concepts/styling-theming.docs.outline.md | 18 ++++++++++-------- docs-src/guides/migrations.docs.outline.md | 4 ++-- docs/concepts/styling-theming.md | 18 ++++++++++-------- docs/migrations.md | 4 ++-- 7 files changed, 33 insertions(+), 27 deletions(-) diff --git a/.changeset/danger-destructive-alias.md b/.changeset/danger-destructive-alias.md index fdfae7647..1022c0da4 100644 --- a/.changeset/danger-destructive-alias.md +++ b/.changeset/danger-destructive-alias.md @@ -4,6 +4,8 @@ `--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. +`--destructive`, `--destructive-foreground` and their utilities (`bg-destructive`, `text-destructive`, `border-destructive`, `text-destructive-foreground`) are deprecated and will be removed in the next major. They keep working in 1.x; new code uses the danger roles (`bg-danger-solid`, `text-danger-text`, `border-danger-border`, `text-danger-contrast`). `Button variant="destructive"` has no replacement yet, so it is not deprecated in this release; the next major renames it to `danger`. + 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`. diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index 0250fe1b9..8a8735789 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -113,7 +113,7 @@ The component variant names (`Badge` and `Alert` `error`, Badge `neutral`) are p 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. +- Names stay in 1.x but are deprecated. `--destructive`, `--destructive-foreground` and their utilities (`bg-destructive`, `text-destructive`, `border-destructive`, `text-destructive-foreground`) keep working and will be removed in the next major, the same as `--status-*` and `--alert-*` ([platform-planning#2084](https://github.com/tailor-inc/platform-planning/issues/2084)). Keeping a second name for one colour leaves two vocabularies, and `text-destructive` is below 4.5:1 as text on the dark card. `text-danger-text` is the role to prefer for text. `Button variant="destructive"` is not deprecated yet because there is no `danger` variant to move to; the next major renames it. - 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`. diff --git a/docs-manifest.json b/docs-manifest.json index d1bcf9695..7f588adbb 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1049,9 +1049,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "8f4b40e6eb2e5381", + "outline": "16c1723900750d80", "snapshot": null, - "outputMd": "80b53c137713bde8", + "outputMd": "f095aa7055b83481", "examples": null } }, @@ -1304,9 +1304,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "ef379deb65bcba3e", + "outline": "dcf033e2a63f0f03", "snapshot": null, - "outputMd": "74855a46c0284abc", + "outputMd": "3ebd9f5828c49ed7", "examples": "4d19f4f688e0f0ac" } }, @@ -1676,7 +1676,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": "e342ef02dc2b9b86", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "74855a46c0284abc", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "3ebd9f5828c49ed7", "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", @@ -1756,7 +1756,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": "d5ca48664fedae18", + "packages/core/skills/app-shell-patterns/references/migrations.md": "afad0935852bec9f", "packages/core/skills/app-shell-patterns/SKILL.md": "526decbfd2db6555" } } diff --git a/docs-src/concepts/styling-theming.docs.outline.md b/docs-src/concepts/styling-theming.docs.outline.md index 6acb9b572..70f6ad9f1 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -147,14 +147,16 @@ Surfaces are named by **role**, not by depth — there is no numbered `surface-1 #### Brand & action -| Token | Use | Tailwind | -| -------------------------------------------- | -------------------------------- | -------------------------------------------- | -| `--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` | 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`. +| Token | Use | Tailwind | +| -------------------------------------------- | ------------------------------------------- | -------------------------------------------- | +| `--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` | deprecated alias of danger solid / contrast | `bg-destructive` / `text-destructive` | + +**Deprecated.** `--destructive`, `--destructive-foreground` and their utilities remain for existing code and will be removed in the next major. New code uses the danger roles: `bg-danger-solid`, `text-danger-contrast`, and `text-danger-text` for text. `Button variant="destructive"` is not deprecated yet; the next major renames it to `danger`. + +`--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, use `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. diff --git a/docs-src/guides/migrations.docs.outline.md b/docs-src/guides/migrations.docs.outline.md index 47b4b637b..f11e727ed 100644 --- a/docs-src/guides/migrations.docs.outline.md +++ b/docs-src/guides/migrations.docs.outline.md @@ -32,7 +32,7 @@ Each entry states which versions are affected, what breaks, how to detect it, an **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: +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. Nothing errors. `--destructive`, `--destructive-foreground` and their utilities are deprecated and will be removed in the next major; `Button variant="destructive"` will be renamed to `danger` then. 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`. @@ -42,7 +42,7 @@ How to detect it: search the app for `destructive` in class names and in theme C 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 text, replace `text-destructive` with `text-danger-text`. For fills, replace `bg-destructive` with `bg-danger-solid`, and `text-destructive-foreground` with `text-danger-contrast`. - 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. diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index af168cc80..3c7380969 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -164,14 +164,16 @@ Surfaces are named by **role**, not by depth — there is no numbered `surface-1 #### Brand & action -| Token | Use | Tailwind | -| -------------------------------------------- | -------------------------------- | -------------------------------------------- | -| `--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` | 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`. +| Token | Use | Tailwind | +| -------------------------------------------- | ------------------------------------------- | -------------------------------------------- | +| `--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` | deprecated alias of danger solid / contrast | `bg-destructive` / `text-destructive` | + +**Deprecated.** `--destructive`, `--destructive-foreground` and their utilities remain for existing code and will be removed in the next major. New code uses the danger roles: `bg-danger-solid`, `text-danger-contrast`, and `text-danger-text` for text. `Button variant="destructive"` is not deprecated yet; the next major renames it to `danger`. + +`--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, use `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. diff --git a/docs/migrations.md b/docs/migrations.md index a0849155f..9f25f361e 100644 --- a/docs/migrations.md +++ b/docs/migrations.md @@ -32,7 +32,7 @@ Each entry states which versions are affected, what breaks, how to detect it, an **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: +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. Nothing errors. `--destructive`, `--destructive-foreground` and their utilities are deprecated and will be removed in the next major; `Button variant="destructive"` will be renamed to `danger` then. 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`. @@ -42,7 +42,7 @@ How to detect it: search the app for `destructive` in class names and in theme C 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 text, replace `text-destructive` with `text-danger-text`. For fills, replace `bg-destructive` with `bg-danger-solid`, and `text-destructive-foreground` with `text-danger-contrast`. - 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. From 1bc524a8d844b4bb7d93c080d58a7ccde04de16a Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 14 Oct 2026 12:43:03 +0900 Subject: [PATCH 11/11] docs(core): list the destructive migration under 1.17.0 and fix the border replacement The destructive change ships in the same release as the colour roles, so the migration entry is 1.17.0. In the changeset, border-destructive maps to border-danger-solid for invalid states; border-danger-border is 1.67:1 on the card and is for decorative borders only. Co-Authored-By: Claude Opus 5.5 --- .changeset/danger-destructive-alias.md | 2 +- docs-manifest.json | 6 +++--- docs-src/guides/migrations.docs.outline.md | 6 +++--- docs/migrations.md | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.changeset/danger-destructive-alias.md b/.changeset/danger-destructive-alias.md index 1022c0da4..0d41a57d4 100644 --- a/.changeset/danger-destructive-alias.md +++ b/.changeset/danger-destructive-alias.md @@ -4,7 +4,7 @@ `--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. -`--destructive`, `--destructive-foreground` and their utilities (`bg-destructive`, `text-destructive`, `border-destructive`, `text-destructive-foreground`) are deprecated and will be removed in the next major. They keep working in 1.x; new code uses the danger roles (`bg-danger-solid`, `text-danger-text`, `border-danger-border`, `text-danger-contrast`). `Button variant="destructive"` has no replacement yet, so it is not deprecated in this release; the next major renames it to `danger`. +`--destructive`, `--destructive-foreground` and their utilities (`bg-destructive`, `text-destructive`, `border-destructive`, `text-destructive-foreground`) are deprecated and will be removed in the next major. They keep working in 1.x; new code uses the danger roles: `bg-danger-solid` for fills, `text-danger-text` for text, `text-danger-contrast` for text on the solid, and `border-danger-solid` for invalid-state borders (`border-danger-border` is for decorative borders only; it is 1.67:1 on the card). `Button variant="destructive"` has no replacement yet, so it is not deprecated in this release; the next major renames it to `danger`. Visible changes in the default palette: diff --git a/docs-manifest.json b/docs-manifest.json index e5e711622..b38cdc2ed 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1049,9 +1049,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "16c1723900750d80", + "outline": "7d63cce8e5d0428b", "snapshot": null, - "outputMd": "f095aa7055b83481", + "outputMd": "6fefe6caff23f403", "examples": null } }, @@ -1756,7 +1756,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": "afad0935852bec9f", + "packages/core/skills/app-shell-patterns/references/migrations.md": "188801b60d8e5a76", "packages/core/skills/app-shell-patterns/SKILL.md": "526decbfd2db6555" } } diff --git a/docs-src/guides/migrations.docs.outline.md b/docs-src/guides/migrations.docs.outline.md index f11e727ed..24d978693 100644 --- a/docs-src/guides/migrations.docs.outline.md +++ b/docs-src/guides/migrations.docs.outline.md @@ -15,7 +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 | [Destructive colours now resolve to the danger roles](#1170-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) | @@ -28,11 +28,11 @@ 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 +## 1.17.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. Nothing errors. `--destructive`, `--destructive-foreground` and their utilities are deprecated and will be removed in the next major; `Button variant="destructive"` will be renamed to `danger` then. Three things change silently: +1.17.0 makes `--destructive` and `--destructive-foreground` aliases of `--danger-solid` and `--danger-contrast`. The names, `Button variant="destructive"` and `text-destructive` keep working. Nothing errors. `--destructive`, `--destructive-foreground` and their utilities are deprecated and will be removed in the next major; `Button variant="destructive"` will be renamed to `danger` then. 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`. diff --git a/docs/migrations.md b/docs/migrations.md index 9f25f361e..b27f0ef99 100644 --- a/docs/migrations.md +++ b/docs/migrations.md @@ -15,7 +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 | [Destructive colours now resolve to the danger roles](#1170-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) | @@ -28,11 +28,11 @@ 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 +## 1.17.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. Nothing errors. `--destructive`, `--destructive-foreground` and their utilities are deprecated and will be removed in the next major; `Button variant="destructive"` will be renamed to `danger` then. Three things change silently: +1.17.0 makes `--destructive` and `--destructive-foreground` aliases of `--danger-solid` and `--danger-contrast`. The names, `Button variant="destructive"` and `text-destructive` keep working. Nothing errors. `--destructive`, `--destructive-foreground` and their utilities are deprecated and will be removed in the next major; `Button variant="destructive"` will be renamed to `danger` then. 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`.