diff --git a/.changeset/floppy-actors-show.md b/.changeset/floppy-actors-show.md
new file mode 100644
index 0000000000..95bb711660
--- /dev/null
+++ b/.changeset/floppy-actors-show.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Added `destructive` prop to Button for dangerous actions like delete or remove. Works with all variants (primary, secondary, tertiary).
+
+**Affected components:** Button
diff --git a/docs-ui/src/app/components/button/components.tsx b/docs-ui/src/app/components/button/components.tsx
index 558c852e9c..1fe13f6630 100644
--- a/docs-ui/src/app/components/button/components.tsx
+++ b/docs-ui/src/app/components/button/components.tsx
@@ -59,6 +59,22 @@ export const Disabled = () => {
);
};
+export const Destructive = () => {
+ return (
+
+
+
+
+
+ );
+};
+
export const Loading = () => {
return (
`;
+export const destructiveSnippet = `
+
+
+
+`;
+
export const loadingSnippet = ``;
diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md
index b6f1a173ef..b4211d2499 100644
--- a/packages/ui/report.api.md
+++ b/packages/ui/report.api.md
@@ -330,6 +330,9 @@ export const ButtonDefinition: {
readonly dataAttribute: true;
readonly default: 'primary';
};
+ readonly destructive: {
+ readonly dataAttribute: true;
+ };
readonly loading: {
readonly dataAttribute: true;
};
@@ -445,6 +448,7 @@ export interface ButtonLinkProps
export type ButtonOwnProps = LeafSurfaceProps & {
size?: Responsive<'small' | 'medium'>;
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
+ destructive?: boolean;
iconStart?: ReactElement;
iconEnd?: ReactElement;
loading?: boolean;
diff --git a/packages/ui/src/components/Button/Button.module.css b/packages/ui/src/components/Button/Button.module.css
index fd57682406..740e18f59e 100644
--- a/packages/ui/src/components/Button/Button.module.css
+++ b/packages/ui/src/components/Button/Button.module.css
@@ -79,6 +79,39 @@
}
}
+ .bui-Button[data-variant='primary'][data-destructive='true'] {
+ /* Custom properties matching future token names (without bui- prefix) */
+ --bg-solid-danger: #dc2626;
+ --bg-solid-danger-hover: #b91c1c;
+ --bg-solid-danger-pressed: #991b1b;
+ --bg-solid-danger-disabled: #fca5a5;
+ --fg-solid-danger: var(--bui-white);
+
+ [data-theme-mode='dark'] & {
+ --bg-solid-danger: #ef4444;
+ --bg-solid-danger-hover: #dc2626;
+ --bg-solid-danger-pressed: #b91c1c;
+ --bg-solid-danger-disabled: #7f1d1d;
+ }
+
+ --bg: var(--bg-solid-danger);
+ --bg-hover: var(--bg-solid-danger-hover);
+ --bg-active: var(--bg-solid-danger-pressed);
+ --fg: var(--fg-solid-danger);
+
+ &[data-disabled='true'],
+ &[data-loading='true'] {
+ --bg: var(--bg-solid-danger-disabled);
+ --bg-hover: var(--bg-solid-danger-disabled);
+ --bg-active: var(--bg-solid-danger-disabled);
+ }
+
+ &:focus-visible {
+ outline: 2px solid var(--bui-border-danger);
+ outline-offset: 2px;
+ }
+ }
+
.bui-Button[data-variant='secondary'] {
--bg: var(--bui-bg-neutral-on-surface-0);
--bg-hover: var(--bui-bg-neutral-on-surface-0-hover);
@@ -117,6 +150,33 @@
}
}
+ .bui-Button[data-variant='secondary'][data-destructive='true'] {
+ /* Custom properties for hover/active states (no tokens exist yet) */
+ --bg-danger-hover: #fecaca;
+ --bg-danger-pressed: #fca5a5;
+
+ [data-theme-mode='dark'] & {
+ --bg-danger-hover: #450a0a;
+ --bg-danger-pressed: #7f1d1d;
+ }
+
+ --bg: var(--bui-bg-danger);
+ --bg-hover: var(--bg-danger-hover);
+ --bg-active: var(--bg-danger-pressed);
+ --fg: var(--bui-fg-danger);
+
+ &[data-disabled='true'],
+ &[data-loading='true'] {
+ --bg-hover: var(--bg);
+ --bg-active: var(--bg);
+ --fg: var(--bui-fg-disabled);
+ }
+
+ &:focus-visible {
+ box-shadow: inset 0 0 0 2px var(--bui-border-danger);
+ }
+ }
+
.bui-Button[data-variant='tertiary'] {
--bg-hover: var(--bui-bg-neutral-on-surface-0-hover);
--bg-active: var(--bui-bg-neutral-on-surface-0-pressed);
@@ -151,6 +211,32 @@
}
}
+ .bui-Button[data-variant='tertiary'][data-destructive='true'] {
+ /* Custom properties for hover/active states (no tokens exist yet) */
+ --bg-danger-hover: #fecaca;
+ --bg-danger-pressed: #fca5a5;
+
+ [data-theme-mode='dark'] & {
+ --bg-danger-hover: #450a0a;
+ --bg-danger-pressed: #7f1d1d;
+ }
+
+ --bg-hover: var(--bui-bg-danger);
+ --bg-active: var(--bg-danger-pressed);
+ --fg: var(--bui-fg-danger);
+
+ &[data-disabled='true'],
+ &[data-loading='true'] {
+ --bg-hover: var(--bg);
+ --bg-active: var(--bg);
+ --fg: var(--bui-fg-disabled);
+ }
+
+ &:focus-visible {
+ box-shadow: inset 0 0 0 2px var(--bui-border-danger);
+ }
+ }
+
.bui-Button[data-size='small'] {
font-size: var(--bui-font-size-3);
padding: 0 var(--bui-space-2);
diff --git a/packages/ui/src/components/Button/Button.stories.tsx b/packages/ui/src/components/Button/Button.stories.tsx
index 22d21b08f9..8d5a67c9fe 100644
--- a/packages/ui/src/components/Button/Button.stories.tsx
+++ b/packages/ui/src/components/Button/Button.stories.tsx
@@ -31,7 +31,10 @@ const meta = preview.meta({
},
variant: {
control: 'select',
- options: ['primary', 'secondary'],
+ options: ['primary', 'secondary', 'tertiary'],
+ },
+ destructive: {
+ control: 'boolean',
},
},
});
@@ -67,6 +70,15 @@ export const Variants = meta.story({
} variant="tertiary">
Button
+ } variant="primary" destructive>
+ Button
+
+ } variant="secondary" destructive>
+ Button
+
+ } variant="tertiary" destructive>
+ Button
+
@@ -81,6 +93,15 @@ export const Variants = meta.story({
} variant="tertiary">
Button
+ } variant="primary" destructive>
+ Button
+
+ } variant="secondary" destructive>
+ Button
+
+ } variant="tertiary" destructive>
+ Button
+
@@ -95,6 +116,15 @@ export const Variants = meta.story({
} variant="tertiary">
Button
+ } variant="primary" destructive>
+ Button
+
+ } variant="secondary" destructive>
+ Button
+
+ } variant="tertiary" destructive>
+ Button
+
@@ -109,6 +139,15 @@ export const Variants = meta.story({
} variant="tertiary">
Button
+ } variant="primary" destructive>
+ Button
+
+ } variant="secondary" destructive>
+ Button
+
+ } variant="tertiary" destructive>
+ Button
+
@@ -123,6 +162,98 @@ export const Variants = meta.story({
} variant="tertiary">
Button
+ } variant="primary" destructive>
+ Button
+
+ } variant="secondary" destructive>
+ Button
+
+ } variant="tertiary" destructive>
+ Button
+
+
+
+
+ ),
+});
+
+export const Destructive = meta.story({
+ render: () => (
+
+
+ Primary Destructive
+
+
+ }>
+ Delete
+
+
+
+
+
+
+ Secondary Destructive
+
+
+ }>
+ Delete
+
+
+
+
+
+
+ Tertiary Destructive
+
+
+ }>
+ Delete
+
+
+
+
+
+
+ On Surface 1
+
+
+
+
+
+
+
+ Sizes
+
+
+
@@ -181,16 +312,29 @@ export const FullWidth = meta.story({
export const Disabled = meta.story({
render: () => (
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
),
});
@@ -270,6 +414,24 @@ export const LoadingVariants = meta.story({
+ Primary Destructive
+
+
+
+ }
+ >
+ With Icon
+
+
+
Loading vs Disabled