From 8028ed1905fdb9e5e65b7af0522e8a363abb71ad Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Tue, 6 Oct 2026 20:47:14 +0900 Subject: [PATCH 1/9] 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 849f012b..0f90e6c8 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 2/9] 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 ec37bdd6..d8afb2d2 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 a99167cb..a4d5773f 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 3/9] 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 168c94a2..1b8a98b8 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 712afaab..2022f522 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 667c9fd4..c9716094 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 3ff3449a..27cf8920 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 3cc9b925..ff2193f2 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 97271cde..d5df128c 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 d83455e9..fb2090b3 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 9f1d49f4..aa8c01ba 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 4/9] 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 27cf8920..775bb8ad 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 70c8e17e..1baf2984 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 d8c00a8e..b3574ff3 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 ff2193f2..9cab6cf9 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 fb2090b3..e43cc56b 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 8c445dbe..6da6f0ac 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 adec256b..1f7c29a5 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 5/9] 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 1b8a98b8..1883f6b5 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 775bb8ad..96e75003 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 9cab6cf9..23627025 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 6/9] 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 97899dee..e22dcd1a 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 6783295f..882c0e51 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 2835473a..6494250e 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 3d582870..07d93cd6 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 7/9] 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 bce0f5fe..a6bedcf5 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 c172b731..4663cab3 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 7c10d206..e8a58519 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 ea542ba0..4464f41d 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 134c8419..1e0dc0b9 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 d86f84a4..b00884b4 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 50a1a673..8e650a7b 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 0f907a1f..d3ccf746 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 47cb882d..b7a1e8b4 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 95bd0378..8534f44c 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 61974dd1..879a63ee 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 d8afb2d2..d18a4c3b 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 8/9] 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 23927ead..0250fe1b 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 dbff2066..79de7d83 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 dff987ad..b8f05fc1 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 cca666b2..47b4b637 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 b553fc46..78aeee1f 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 4ffc9154..a0849155 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 9/9] 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 00000000..fdfae764 --- /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.