From d572ea39390390ba9af74710bdb51f023e52e837 Mon Sep 17 00:00:00 2001 From: Ravi Suhag Date: Wed, 7 Oct 2026 19:08:33 -0500 Subject: [PATCH 1/2] fix: [select] mark the selected item and allow alignItemWithTrigger --- .../content/docs/components/select/index.mdx | 5 ++- .../content/docs/components/select/props.ts | 6 ++++ .../select/__tests__/data-slots.test.tsx | 7 ++-- .../select/__tests__/select.test.tsx | 32 +++++++++++++++++++ .../components/select/select-content.tsx | 3 +- .../react/components/select/select-item.tsx | 26 ++++++++++++++- .../react/components/select/select.module.css | 18 +++++++++++ 7 files changed, 91 insertions(+), 6 deletions(-) diff --git a/apps/www/src/content/docs/components/select/index.mdx b/apps/www/src/content/docs/components/select/index.mdx index 2c2925bff..70b2fd9e1 100644 --- a/apps/www/src/content/docs/components/select/index.mdx +++ b/apps/www/src/content/docs/components/select/index.mdx @@ -43,7 +43,7 @@ Appearance first, meaning size and variant, then how many items can be chosen, a ### Basic Select -A trigger and a list of items. `Select.Value` renders the current selection in the trigger. +A trigger and a list of items. `Select.Value` renders the current selection in the trigger. A check mark shows the selected item in the list. In multiple mode, each item has a checkbox instead. @@ -117,6 +117,8 @@ The container that holds the Select items. +Set `alignItemWithTrigger` to open the list over the trigger, with the selected item lined up on the trigger's value. Base UI turns it off for touch input and when there is not enough space. `side` and `align` have no effect while it is on. + ### Item Individual clickable options within the Select. @@ -164,6 +166,7 @@ Every rendered part carries a stable `data-slot` attribute for [styling and test | `select-item` | An individual option row | | `select-item-icon` | Leading icon on an item (when set) | | `select-item-text` | The text label of an item | +| `select-item-indicator` | The check mark on the selected item (single selection only) | ## Accessibility diff --git a/apps/www/src/content/docs/components/select/props.ts b/apps/www/src/content/docs/components/select/props.ts index 80ddce754..14924698a 100644 --- a/apps/www/src/content/docs/components/select/props.ts +++ b/apps/www/src/content/docs/components/select/props.ts @@ -79,6 +79,12 @@ export interface SelectContentProps { */ sideOffset?: number; + /** + * Overlaps the trigger so the selected item's text lines up with the trigger's value. Has no effect in autocomplete mode. + * @default false + */ + alignItemWithTrigger?: boolean; + /** Additional CSS class names. */ className?: string; diff --git a/packages/react/components/select/__tests__/data-slots.test.tsx b/packages/react/components/select/__tests__/data-slots.test.tsx index d26d23883..9d3cbf254 100644 --- a/packages/react/components/select/__tests__/data-slots.test.tsx +++ b/packages/react/components/select/__tests__/data-slots.test.tsx @@ -45,10 +45,10 @@ describe('Select data-slot contract', () => { ]); }); - it('exposes content, list, group, label, separator and item slots when open', async () => { + it('exposes content, list, group, label, separator, item and indicator slots when open', async () => { const user = userEvent.setup(); render( - @@ -76,7 +76,8 @@ describe('Select data-slot contract', () => { 'select-label', 'select-separator', 'select-item', - 'select-item-text' + 'select-item-text', + 'select-item-indicator' ]); }); diff --git a/packages/react/components/select/__tests__/select.test.tsx b/packages/react/components/select/__tests__/select.test.tsx index ebf572071..33ff2e7c4 100644 --- a/packages/react/components/select/__tests__/select.test.tsx +++ b/packages/react/components/select/__tests__/select.test.tsx @@ -81,6 +81,23 @@ describe('Select', () => { }); describe('Single Selection', () => { + it('marks only the selected item with an indicator', async () => { + const user = userEvent.setup(); + render(); + await flushMicrotasks(); + await openSelect(user); + await flushMicrotasks(); + + const options = screen.getAllByRole('option'); + expect( + options[1].querySelector('[data-slot="select-item-indicator"]') + ).not.toBeNull(); + expect( + options[0].querySelector('[data-slot="select-item-indicator"]') + ).toBeNull(); + expect(within(options[1]).queryByRole('checkbox')).toBeNull(); + }); + it('displays selected value in trigger', async () => { render(); await flushMicrotasks(); @@ -281,6 +298,21 @@ describe('Select', () => { expect(searchInput).toBeInTheDocument(); }); + it('marks the selected item with an indicator', async () => { + render(); + + fireEvent.click(screen.getByLabelText('Select option')); + await flushMicrotasks(); + + const options = screen.getAllByRole('option'); + expect( + options[1].querySelector('[data-slot="select-item-indicator"]') + ).not.toBeNull(); + expect( + options[0].querySelector('[data-slot="select-item-indicator"]') + ).toBeNull(); + }); + it('filters options based on search', async () => { const user = userEvent.setup(); render(); diff --git a/packages/react/components/select/select-content.tsx b/packages/react/components/select/select-content.tsx index 1b6e20a52..123d10e99 100644 --- a/packages/react/components/select/select-content.tsx +++ b/packages/react/components/select/select-content.tsx @@ -28,6 +28,7 @@ export function SelectContent({ sideOffset = 4, side = 'bottom', align = 'start', + alignItemWithTrigger = false, radius, ...props }: SelectContentProps) { @@ -80,7 +81,7 @@ export function SelectContent({ side={side} align={align} className={styles.positioner} - alignItemWithTrigger={false} + alignItemWithTrigger={alignItemWithTrigger} data-slot='select-positioner' > )} - {children} + {autocomplete ? ( + + {children} + + ) : ( + } + className={styles.itemText} + data-slot='select-item-text' + > + {children} + + )} ) : ( children @@ -70,6 +83,9 @@ export function SelectItem({ const ItemPrimitive = autocomplete ? ComboboxPrimitive.Item : SelectPrimitive.Item; + const IndicatorPrimitive = autocomplete + ? ComboboxPrimitive.ItemIndicator + : SelectPrimitive.ItemIndicator; return ( {multiple && } {element} + {!multiple && ( + + + )} )} /> diff --git a/packages/react/components/select/select.module.css b/packages/react/components/select/select.module.css index b5f5ae2fb..b159a565e 100644 --- a/packages/react/components/select/select.module.css +++ b/packages/react/components/select/select.module.css @@ -286,3 +286,21 @@ .hidden { display: none; } + +.itemText { + flex: 1; + min-width: 0; +} + +.itemIndicator { + display: flex; + align-items: center; + flex-shrink: 0; + margin-left: auto; + color: var(--rs-color-foreground-base-primary); +} + +.itemIndicator svg { + width: var(--rs-space-5); + height: var(--rs-space-5); +} From 33fb330ea417f021151fba77d2515a0847db2130 Mon Sep 17 00:00:00 2001 From: Ravi Suhag Date: Wed, 7 Oct 2026 20:11:44 -0500 Subject: [PATCH 2/2] fix: [docs] clarify when select side and align apply --- apps/www/src/content/docs/components/select/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/src/content/docs/components/select/index.mdx b/apps/www/src/content/docs/components/select/index.mdx index 70b2fd9e1..ef6f79097 100644 --- a/apps/www/src/content/docs/components/select/index.mdx +++ b/apps/www/src/content/docs/components/select/index.mdx @@ -117,7 +117,7 @@ The container that holds the Select items. -Set `alignItemWithTrigger` to open the list over the trigger, with the selected item lined up on the trigger's value. Base UI turns it off for touch input and when there is not enough space. `side` and `align` have no effect while it is on. +Set `alignItemWithTrigger` to open the list over the trigger, with the selected item lined up on the trigger's value. Base UI turns it off for touch input and when there is not enough space, and then places the list the normal way. `side` and `align` only apply when the list is placed the normal way. ### Item