From d2fddedd3e98395ebfe44abbc1f7f7575af3c7db Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 19 Jan 2026 16:39:08 +0100 Subject: [PATCH] ui: add indeterminate state to Checkbox component Add support for the indeterminate state in the Checkbox component, displaying a horizontal dash icon instead of a checkmark. This is useful for "select all" scenarios in tables where only some rows are selected. The indeterminate state maintains the unchecked visual style (white background with border) while showing the dash icon. Signed-off-by: Johan Persson --- .changeset/brown-grapes-fry.md | 7 ++++++ packages/ui/report.api.md | 1 + .../components/Checkbox/Checkbox.module.css | 9 ++++++- .../components/Checkbox/Checkbox.stories.tsx | 11 ++++++++ .../ui/src/components/Checkbox/Checkbox.tsx | 25 +++++++++++++------ .../ui/src/components/Checkbox/definition.ts | 1 + 6 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 .changeset/brown-grapes-fry.md diff --git a/.changeset/brown-grapes-fry.md b/.changeset/brown-grapes-fry.md new file mode 100644 index 0000000000..c2fd1fbe2a --- /dev/null +++ b/.changeset/brown-grapes-fry.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Added indeterminate state support to the Checkbox component for handling partial selection scenarios like table header checkboxes. + +Affected components: Checkbox diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index ca9d2bcff1..fa50111cde 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -460,6 +460,7 @@ export const CheckboxDefinition: { }; readonly dataAttributes: { readonly selected: readonly [true, false]; + readonly indeterminate: readonly [true, false]; }; }; diff --git a/packages/ui/src/components/Checkbox/Checkbox.module.css b/packages/ui/src/components/Checkbox/Checkbox.module.css index 23a88d2b80..a4647c935a 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.module.css +++ b/packages/ui/src/components/Checkbox/Checkbox.module.css @@ -62,7 +62,14 @@ color: var(--bui-fg-solid); } - .bui-Checkbox[data-hovered]:not([data-selected]) & { + .bui-Checkbox[data-indeterminate] & { + background-color: var(--bui-bg-surface-1); + box-shadow: inset 0 0 0 1px var(--bui-border); + color: var(--bui-fg-primary); + } + + .bui-Checkbox[data-hovered]:not([data-selected]):not([data-indeterminate]) + & { box-shadow: inset 0 0 0 1px var(--bui-border-hover); } diff --git a/packages/ui/src/components/Checkbox/Checkbox.stories.tsx b/packages/ui/src/components/Checkbox/Checkbox.stories.tsx index c5318cc6fb..e8f7859785 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.stories.tsx +++ b/packages/ui/src/components/Checkbox/Checkbox.stories.tsx @@ -28,16 +28,27 @@ export const Default = meta.story({ }, }); +export const Indeterminate = meta.story({ + args: { + children: 'Select all', + isIndeterminate: true, + }, +}); + export const AllVariants = meta.story({ ...Default.input, render: () => ( Unchecked Checked + Indeterminate Disabled Checked & Disabled + + Indeterminate & Disabled + ), }); diff --git a/packages/ui/src/components/Checkbox/Checkbox.tsx b/packages/ui/src/components/Checkbox/Checkbox.tsx index 97a603daf2..6030fbc06b 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.tsx +++ b/packages/ui/src/components/Checkbox/Checkbox.tsx @@ -21,7 +21,7 @@ import { useStyles } from '../../hooks/useStyles'; import { CheckboxDefinition } from './definition'; import clsx from 'clsx'; import styles from './Checkbox.module.css'; -import { RiCheckLine } from '@remixicon/react'; +import { RiCheckLine, RiSubtractLine } from '@remixicon/react'; /** @public */ export const Checkbox = forwardRef( @@ -35,12 +35,23 @@ export const Checkbox = forwardRef( className={clsx(classNames.root, styles[classNames.root], className)} {...rest} > -
- -
- {children} + {({ isIndeterminate }) => ( + <> +
+ {isIndeterminate ? ( + + ) : ( + + )} +
+ {children} + + )} ); }, diff --git a/packages/ui/src/components/Checkbox/definition.ts b/packages/ui/src/components/Checkbox/definition.ts index 022f2a0af1..c7b5b16276 100644 --- a/packages/ui/src/components/Checkbox/definition.ts +++ b/packages/ui/src/components/Checkbox/definition.ts @@ -27,5 +27,6 @@ export const CheckboxDefinition = { }, dataAttributes: { selected: [true, false] as const, + indeterminate: [true, false] as const, }, } as const satisfies ComponentDefinition;