← All proposals

Card Select

Add a shadow prop to the PDS Card Select.

Extends a PDS componentOpen in Storybook

proposals/CardSelect/HANDOFF.md72 lines · View on GitHub

Handoff: CardSelect

What and why

The PDS Card Select with a shadow prop. Every card has a drop shadow today; in dense layouts and on surfaces that already carry elevation it is too heavy. shadow={true} (default) is the unchanged PDS look. shadow={false} removes the shadow and draws the card with a border in the input border color, a background tint on hover, and, when the radio indicator is hidden, a focus-color ring on the selected card. The proposal wraps the published CardSelect; PDS is not changed.

  • Figma: Card Select page in PDS-Proposals, with two real components: Card select item (set 665:265: Shadow On/Off x State Default/Hover/Selected x Show Indicator True/False, built like the library item with a placeholder slot and no text) and Card Select (666:213: a label and a row of three exposed cards). Jira: none. Source: a brief from the designer, 2026-11-03.
  • Context: context.md

Exploration and decisions

  • Wrap, not extend. Same concept and role, only the look changes; the markup, radio inputs and keyboard behavior come from PDS. The port adds one prop and some CSS (see Port notes).
  • A border replaces the shadow. The PDS raised shadow already includes a 1px edge ring, so removing it alone leaves a white card invisible on a white surface. The border uses --pds-color-border-input, so a card reads like the other form controls.
  • Hover is the secondary Button hover. --pds-color-button-secondary-background-hover, layered over the card background (background-image), so the default, secondary and transparent backgrounds keep their fill.
  • The blue outline is saved for the no-indicator state. With the indicator, the check icon marks the selected card and the border stays; without it, the focus-color ring does.
  • Figma follows Storybook, not the PDS Figma library. The library Card select item pads 16 on all sides; the PDS code pads 20 and 24. The Figma item pads 20 and 24 (primitive/spacing/L and XL). Design: the library item should be updated to match the code.
  • The shadow is on the card. In Storybook shadow is a prop of CardSelect and applies to every card; in Figma Shadow is a property of the item and is set per card.
  • Figma indicator color is a token-bound fill on the library circle icon (semantic/border/input; the success fill on circleCheck), because the icon has no color property.
  • Show Indicator is a variant in Figma, not a boolean, because it changes the selected card's border as well as the circle.
  • Tried and not done: a prop on the card (the shadow is on each card, but the group is the API).

API

Prop Type Default Purpose
shadow boolean true Show the drop shadow on each card. false: border in the input color, hover tint, and the focus ring on the selected card when showIndicator is false.

Everything else is the PDS CardSelect (background, defaultValue, id, imageVariant, label, labelDisplay, layout, onChange, options, padding, showCardLabels, showIndicator, size).

Variants and states: shadow on, shadow off, shadow off without the indicator, shadow off at size l; unselected, hover, selected.

Tokens used

  • --pds-border-width-default
  • --pds-color-border-input
  • --pds-color-button-secondary-background-hover
  • --pds-color-interactive-focus

The rest come from the PDS Card Select itself.

Impact

From impact.md: nothing reaches existing components; the additions are scoped to .pds-card-select--no-shadow. No other proposal imports or uses this one.

Port order (docs/PORT-ORDER.md): later, 17th, an extension of the PDS Card Select. It is not tagged ready for dev yet.

Reused vs net-new

  • Reused: the PDS CardSelect (unchanged), all its props and its tokens.
  • Net-new: the prop shadow and the modifier .pds-card-select--no-shadow (see custom.md).

Accessibility

  • The radio inputs, labels, legend and keyboard behavior are the PDS Card Select's. Arrow keys move the selection; the focus ring (outline, 2px offset) is drawn on the card when its radio is focused, in both modes.
  • The unselected border (--pds-color-border-input) is about 1.5:1 against a white surface, below the 3:1 usually asked of a control boundary. It is the same token the PDS inputs use, and the selected state does not rely on it (check icon, or the focus ring). Noted for the designer; unchanged.
  • axe-core (the engine behind the Storybook a11y panel) found no violations, 2026-11-03: all four stories (With Shadow, Without Shadow, Without Shadow Or Indicator, Without Shadow Large) in light and dark. Also checked: computed styles of all stories in light mode and the dark values; keyboard focus on the no-indicator story; 375px width with no horizontal scroll. Not checked: dark-mode hover and ring colors in the Theme toolbar by eye, and very long card text.

Decisions and open questions

  • Not built in the Figma item: Size, Disabled, background, padding, imageVariant, Show Card Labels (they exist in Storybook and the PDS component).

Port notes

  • Existing component, in pds-toolkit-react under src/components/cards/CardSelect/.
  • Add shadow?: boolean (default true) to CardSelectProps, TSDoc included, and const noShadowClass = !shadow ? \${baseClass}--no-shadow` : null` to the class list.
  • Add the three .pds-card-select--no-shadow rules from card-select.css to the component's card-select.css. The doubled .pds-card-select__option-label.pds-card-select__option-label selector is only here to beat the package rules from outside; in the port, write them as normal single selectors after the existing ones, and keep the ring scoped to --no-indicator.
  • Update the Card Select docs and add a story.

Preview

npm run storybook, stories Component Proposals/Card Select. Figma: Card Select, page Card Select.