Handoff: Select
What and why
An extension of the PDS Select with one new prop, variant: default (unchanged) or bare. Bare has no border, fill or container: the selected value and a chevron sit on the surface, secondary text at rest and default text on hover and while open. It exists for a picker that sits inline in a toolbar, first the model picker in the Chat Input. Choosing one value from a list is what the Select already does (listbox semantics, a check on the selected option, typeahead, keyboard handling); only the look was wrong for a toolbar. It replaces the earlier ModelSelector proposal, which was built on a menu button and is retired. Figma: Select page (the designer's hand-built SelectUtility component set; it does not show the bare variant by name). Context: context.md.
Exploration and decisions
- A variant of the Select, not a new component: a model picker is a Select without the container.
- Built as a thin wrapper around the published
Select(it adds the classpds-select--bareand turnsshowLabeloff), not a copy of its source: a copy needs local versions of six helpers the package does not export and about 600 lines of input styles, for a change that is CSS plus a class. The result for the port is the same. - Bare clears
border-colorandbackground-coloron the trigger wrapper, which keeps the Select's height and focus ring. State shows in the text color, not a fill. - The trigger hugs the selected value (first tried with
autoWidth, which left too much room); the list is as wide as its longest option. - Option descriptions and an
xssize from the old Model Selector were not carried over (one new prop was asked for). - The stories show only the bare variant;
defaultis the existing PDS Select.
Full list and reasons: decisions.md.
API
SelectProps is the published PDS SelectProps plus variant. Only variant is new; the rest are the existing props, unchanged.
| Prop | Type | Default | Purpose |
|---|---|---|---|
variant |
'bare' | 'default' |
'default' |
The look of the trigger. bare has no border, fill or container, hides the label (still read by screen readers), and the trigger hugs the selected value. |
className |
string |
none | Additional class names (merged with pds-select--bare for bare). |
showLabel |
boolean |
true |
Whether the label is visible. Forced to false for bare. |
Existing Select props still apply, among them id and label (required), options (label, value, optional icon and disabled), value with onOptionSelect or defaultValue, size ('s' default, 'm'), disabled, autoWidth, inputWidth, message, validationStatus, footer, labelStrings. See the PDS Select for the full list.
Variants and states: default, bare; sizes s and m; disabled; open; hover. Stories: Bare, AllSizesAndStates.
Tokens used
--pds-color-foreground-default--pds-color-foreground-default-secondary--pds-spacing-2xs--pds-spacing-l(story only)
Impact
From impact.md: no existing component is changed. The additions are opt-in: .pds-select--bare and its rules (the wrapper, inner and trigger-icon rules), which only apply where the class is added, so every existing Select is unaffected. No --pds-* token is redefined. The Chat Input and the Chat with an agent recipe use the bare Select in their stories; ChatInput composes it, so port Select first.
Reused vs net-new
- Reused: the published PDS
Selectin full (behavior, markup and accessibility); the localmergeClassesstand-in (Button/utils). - Net-new:
.pds-select--bare(a modifier on.pds-select) and thevariantprop. No custom CSS variables and no raw values. Details incustom.md.
Accessibility
Unchanged from the PDS Select (a listbox with a labelled trigger, a check on the selected option, typeahead and keyboard handling). Bare keeps the label for screen readers (visually-hidden) and the Select's own focus ring. The state is shown by text color only (secondary to default), so check hover and open contrast in light and dark. The a11y panel has not been run, and light and dark were not checked by eye in a session.
Decisions and open questions
For the engineer or designer to settle:
- Name:
variant='bare'or another word (inline,ghost)? PDS has no precedent for a borderless field. - Do option descriptions or an
xssize matter for the models list? (14px model name in the compact chat bar;xswould be a second addition.) - Validation, message and prefix props still exist on the bare variant but have no place in a toolbar; leave them or ignore them in the bare look?
- The list is widened with a
:has()rule on the body (the list renders in a portal outside the Select), a workaround; the real Select should put a bare-variant class on the list.:has()needs Chrome 105, Safari 15.4 or Firefox 121.
Port notes
Changes the existing PDS Select (src/components/inputs/Select/): add variant: 'default' | 'bare' to its props, apply pds-select--bare (and showLabel off) in the component, add the .pds-select--bare rules to select.css, and put a bare-variant class on the dropdown list instead of the :has() rule. No generator needed. Differences from v2 to undo: the wrapper around the package Select (edit the Select itself), the local mergeClasses (use @libs/components/utils), and select-story.css (story scaffolding only).
Preview
npm run storybook, story Component Proposals / Select. Figma: https://www.figma.com/design/AMGWJbraJoi2PKWnxo29Ah/PDS-Proposals?node-id=291-6855, page ↳ Select (the designer's component set, not a capture).