← All proposals

Select

An extension of the PDS Select: one new prop, variant, with bare next to default.

Extends a PDS componentOpen in Storybook

proposals/Select/HANDOFF.md68 lines · View on GitHub

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 class pds-select--bare and turns showLabel off), 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-color and background-color on 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 xs size from the old Model Selector were not carried over (one new prop was asked for).
  • The stories show only the bare variant; default is 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 Select in full (behavior, markup and accessibility); the local mergeClasses stand-in (Button/utils).
  • Net-new: .pds-select--bare (a modifier on .pds-select) and the variant prop. No custom CSS variables and no raw values. Details in custom.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 xs size matter for the models list? (14px model name in the compact chat bar; xs would 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).