Skip to Content

Selection Group

Selection Group lets users choose one or more options from a predefined list using tappable Selection Chip items.

Interaction Modes

Completing

Configuring

Delegating

Intervening

Refining

Selecting

Install

yarn add @workday/canvas-kit-labs-react

Component Type

Input

Platform

Web

Component

Sana Canvas

Delivery Channels

Web

Version

16.2.12

Experience Surfaces

Page Body Inline

Component Type

Input

Platform

Web

Component

Sana Canvas

Delivery Channels

Web

Version

16.2.12

Experience Surfaces

Page Body Inline

Anatomy

Anatomy image of Selection Group component.
  1. Form Label: The title of the input, describing what the group of chips represents.
  2. Chip Container: Rectangular, rounded container that houses the glyph and the chip label. The entire container is the tap target, not just the glyph.
  3. Glyph: A lightweight, custom glyph, not the native checkbox or radio input. It always renders in both modes, so chips never shift width on selection.
  4. Chip Label: The associated text for each chip. Should be as short and concise as possible.

A custom glyph, rather than the standard checkbox or radio component, keeps the glyph color in sync with the selected label color. It also avoids the visual weight of the larger, green-when-active standard inputs, so it keeps visual noise down.

The shape, circle versus box, communicates the mode. This is the same convention as radio buttons and checkboxes, so users can tell single-select from multi-select before interacting.

Interaction States

Selection Chips support default, hover, active (pressed), focus, selected, and disabled states, for both single-select and multi-select modes.

  • Default: Unselected, resting state.
  • Hover: Subtle background shift to indicate interactivity.
  • Active (pressed): Container fill darkens briefly to confirm the touch/click registered.
  • Focus: A visible focus outline surrounds the chip. There is no selected border. A selected border would conflict with the focus indicator.
  • Selected: Communicated through the checkmark inside the glyph plus a background and text color change. Never through color alone. This satisfies WCAG 1.4.1 (Use of Color).
  • Disabled: Displays in a lighter gray with reduced contrast, indicating the chip cannot be interacted with.

Notification States

Notification states apply to the overall input (the group), not to individual chips. On mobile, notification states include an icon alongside the notification text; web notifications use text only.

Variations

Single Select

Allows selection of only one chip at a time. Functions like a radio button group: selecting a chip deselects any previously selected chip in the group. Indicated by a circle glyph in every chip.

Group label guidance: use language like “Select one.”

Multi-Select

Allows selection of multiple chips at once. Functions like a checkbox group. Indicated by a box glyph in every chip.

Group label guidance: a group label such as “Select all that apply” is required. It is not an optional helper text. Users need to understand that multi-select is available before they interact.

Width Variations

Selection Chips can be sized a few different ways depending on use case and content width. Whichever width you choose, all chips within the group should use the same sizing method. Don’t mix sizing methods within one group. Sizing refers only to the width of individual chips.

Content Width

Each chip is sized based on its own label content, so chips within a group may differ in width. Works well when labels are short.

Equal Width

Every chip in the group is sized to match the width of the longest label, so all chips end up the same width. Works well when labels are short.

Column Width

Every chip spans the full width of the column (full width on mobile). Best for longer labels, or when the Selection Group is the primary or only input on the page. Column-width groups should use fewer chips.

Usage Guidance

Use Selection Group in place of prompts, radio button groups, or checkbox groups when a larger, more scannable tap target and a lighter visual footprint are preferred over standard-size checkbox or radio inputs.

Selection Group allows quick selection of one or more predefined values, using individual Selection Chip items.

The larger perceived tap target improves usability and accessibility over standard radio buttons and checkboxes.

Selection Chips are visually larger than standard radio buttons and checkboxes. They keep visual noise down by matching glyph color to the selected label color, rather than using the larger, green-active standard inputs.

Each chip within a group should be visually and semantically distinct.

Order chips logically: alphabetical, chronological, or by importance.

Consider adding a “None” chip where applicable, especially for required fields.

For multi-select groups, the group label must state that multiple selections are allowed, for example “Select all that apply.” This is required, not optional. It resolves mode ambiguity for users navigating without a mouse.

When to Use

  • The user needs to quickly select from a predefined list.
  • The content of the options is controlled data.
  • Option labels are short and concise.
  • The number of options is roughly 7 or fewer, to avoid overwhelming the user.

When to Use Something Else

  • More than ~7 options: use a standard prompt instead.
  • Only one option: use a checkbox or switch instead.
  • Option labels are long: use standard radio buttons, checkboxes, or a prompt instead.
  • Option labels vary greatly in length from one another: use standard radio buttons, checkboxes, or a prompt instead.
  • The only choices are yes or no: use a switch instead.

Exceptions

  • The ~7-option threshold is a guideline, not a hard rule. Some use cases require more options even when labels are short and the data is controlled. For example, recruiting interview scheduling may need to show candidates more than 7 time slots at once. Flag additional use cases as they come up so this guidance can evolve.

Content Guidelines

The list of chip labels should be scannable, with concise labels written in the title case.

Examples

Basic Example

SelectionGroup includes a container SelectionGroup component and the following subcomponents: SelectionGroup.List and SelectionGroup.Item.

import {SelectionGroup} from '@workday/canvas-kit-labs-react/selection-group';

export default () => (
  <SelectionGroup mode="single">
    <SelectionGroup.List aria-label="Preferred contact method (select one)">
      <SelectionGroup.Item data-id="email">Email</SelectionGroup.Item>
      <SelectionGroup.Item data-id="phone">Phone</SelectionGroup.Item>
      <SelectionGroup.Item data-id="text">Text Message</SelectionGroup.Item>
    </SelectionGroup.List>
  </SelectionGroup>
);

Single Select

Use mode="single" to allow only one selection at a time. Single-select groups use a circle glyph and behave like a radio group: they have a single tab stop and arrow keys move both focus and selection.

Preferred Contact Method (Select one)

Selected: email

import React from 'react';

import {SelectionGroup} from '@workday/canvas-kit-labs-react/selection-group';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {BodyText} from '@workday/canvas-kit-react/text';
import {system} from '@workday/canvas-tokens-web';

export default () => {
  const [selected, setSelected] = React.useState('email');

  return (
    <>
      <FormField as="fieldset">
        <FormField.Label as="legend">Preferred Contact Method (Select one)</FormField.Label>
        <FormField.Input
          as={SelectionGroup}
          mode="single"
          selectedIds={[selected]}
          onSelect={data => setSelected(data.id)}
        >
          <SelectionGroup.List aria-label="Preferred contact method (select one)">
            <SelectionGroup.Item data-id="email">Email</SelectionGroup.Item>
            <SelectionGroup.Item data-id="phone">Phone</SelectionGroup.Item>
            <SelectionGroup.Item data-id="text">Text Message</SelectionGroup.Item>
          </SelectionGroup.List>
        </FormField.Input>
      </FormField>
      <BodyText size="small" cs={{marginBlockStart: system.gap.md}}>
        Selected: {selected}
      </BodyText>
    </>
  );
};

Multi Select

Use mode="multiple" to allow more than one selection. Multi-select groups use a square glyph and behave like a checkbox group, where every item is its own tab stop. The group label must state that multiple selections are allowed.

Available Days (Select all that apply)

Selected: monday

import React from 'react';

import {SelectionGroup} from '@workday/canvas-kit-labs-react/selection-group';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {BodyText} from '@workday/canvas-kit-react/text';
import {system} from '@workday/canvas-tokens-web';

export default () => {
  const [selected, setSelected] = React.useState<string[]>(['monday']);

  const handleSelect = (data: {id: string}) => {
    setSelected(previous =>
      previous.includes(data.id) ? previous.filter(id => id !== data.id) : previous.concat(data.id)
    );
  };

  return (
    <>
      <FormField as="fieldset">
        <FormField.Label as="legend">Available Days (Select all that apply)</FormField.Label>
        <FormField.Input
          as={SelectionGroup}
          mode="multiple"
          selectedIds={selected}
          onSelect={handleSelect}
        >
          <SelectionGroup.List aria-label="Available days (select all that apply)">
            <SelectionGroup.Item data-id="monday">Monday</SelectionGroup.Item>
            <SelectionGroup.Item data-id="tuesday">Tuesday</SelectionGroup.Item>
            <SelectionGroup.Item data-id="wednesday">Wednesday</SelectionGroup.Item>
            <SelectionGroup.Item data-id="thursday">Thursday</SelectionGroup.Item>
            <SelectionGroup.Item data-id="friday">Friday</SelectionGroup.Item>
          </SelectionGroup.List>
        </FormField.Input>
      </FormField>
      <BodyText size="small" cs={{marginBlockStart: system.gap.md}}>
        Selected: {selected.join(', ') || 'None'}
      </BodyText>
    </>
  );
};

Width Variations

All items within a group use the same sizing method — don’t mix sizing methods within one group.

Content Width

Each item is sized to its own label, so items within a group may differ in width.

import {SelectionGroup} from '@workday/canvas-kit-labs-react/selection-group';

export default () => (
  <SelectionGroup mode="single" width="content" initialSelectedIds={['short']}>
    <SelectionGroup.List aria-label="Content width selection group (select one)">
      <SelectionGroup.Item data-id="short">Short</SelectionGroup.Item>
      <SelectionGroup.Item data-id="medium-length">Medium Length</SelectionGroup.Item>
      <SelectionGroup.Item data-id="a-much-longer-label">A Much Longer Label</SelectionGroup.Item>
    </SelectionGroup.List>
  </SelectionGroup>
);

Equal Width

Every item is sized to match the width of the longest label, so all items end up the same width.

import {SelectionGroup} from '@workday/canvas-kit-labs-react/selection-group';

export default () => (
  <SelectionGroup mode="single" width="equal" initialSelectedIds={['short']}>
    <SelectionGroup.List aria-label="Equal width selection group (select one)">
      <SelectionGroup.Item data-id="short">Short</SelectionGroup.Item>
      <SelectionGroup.Item data-id="medium-length">Medium Length</SelectionGroup.Item>
      <SelectionGroup.Item data-id="a-much-longer-label">A Much Longer Label</SelectionGroup.Item>
    </SelectionGroup.List>
  </SelectionGroup>
);

Column Width

Every item spans the full width of the column. Best for longer labels, or when the group is the primary input on the page.

import {SelectionGroup} from '@workday/canvas-kit-labs-react/selection-group';

export default () => (
  <SelectionGroup mode="single" width="column" initialSelectedIds={['option-a']}>
    <SelectionGroup.List aria-label="Column width selection group (select one)">
      <SelectionGroup.Item data-id="option-a">
        Option A with a longer descriptive label
      </SelectionGroup.Item>
      <SelectionGroup.Item data-id="option-b">
        Option B with a longer descriptive label
      </SelectionGroup.Item>
      <SelectionGroup.Item data-id="option-c">
        Option C with a longer descriptive label
      </SelectionGroup.Item>
    </SelectionGroup.List>
  </SelectionGroup>
);

Disabled

Set the disabled prop on SelectionGroup to disable the entire group. To disable individual items, pass their ids to nonInteractiveIds so that keyboard navigation skips over them as well.

The whole group is disabled

A single item is disabled

import {SelectionGroup} from '@workday/canvas-kit-labs-react/selection-group';
import {BodyText} from '@workday/canvas-kit-react/text';
import {system} from '@workday/canvas-tokens-web';

export default () => (
  <>
    <BodyText size="small" cs={{marginBlockEnd: system.gap.xs}}>
      The whole group is disabled
    </BodyText>
    <SelectionGroup mode="single" disabled initialSelectedIds={['option-a']}>
      <SelectionGroup.List aria-label="Fully disabled selection group (select one)">
        <SelectionGroup.Item data-id="option-a">Option A</SelectionGroup.Item>
        <SelectionGroup.Item data-id="option-b">Option B</SelectionGroup.Item>
        <SelectionGroup.Item data-id="option-c">Option C</SelectionGroup.Item>
      </SelectionGroup.List>
    </SelectionGroup>

    <BodyText size="small" cs={{marginBlockStart: system.gap.md, marginBlockEnd: system.gap.xs}}>
      A single item is disabled
    </BodyText>
    <SelectionGroup
      mode="single"
      nonInteractiveIds={['option-c']}
      initialSelectedIds={['option-a']}
    >
      <SelectionGroup.List aria-label="Partially disabled selection group (select one)">
        <SelectionGroup.Item data-id="option-a">Option A</SelectionGroup.Item>
        <SelectionGroup.Item data-id="option-b">Option B</SelectionGroup.Item>
        <SelectionGroup.Item data-id="option-c">Option C</SelectionGroup.Item>
      </SelectionGroup.List>
    </SelectionGroup>
  </>
);

Error

Notification states apply to the whole group rather than to individual items. Wrapping the group in a FormField with error="error" applies the error state and associates the hint text with the group. This is the recommended approach, because FormField also renders the visible hint text, sets aria-invalid, and points the group’s aria-describedby at that text.

You can set error="error" directly on SelectionGroup when you aren’t using FormField, but the prop only changes the group’s border color. Color on its own is not an accessible way to communicate a notification state, so pair it with your own visible text and pass that element’s id to aria-describedby on SelectionGroup.

Select Your Pizza Crust (Select one)

Please select a crust to continue

import React from 'react';

import {SelectionGroup} from '@workday/canvas-kit-labs-react/selection-group';
import {FormField} from '@workday/canvas-kit-react/form-field';

export default () => {
  const [selected, setSelected] = React.useState<string[]>([]);

  return (
    <FormField error="error" as="fieldset">
      <FormField.Label as="legend">Select Your Pizza Crust (Select one)</FormField.Label>
      <FormField.Input
        as={SelectionGroup}
        mode="single"
        selectedIds={selected}
        onSelect={data => setSelected([data.id])}
      >
        <SelectionGroup.List aria-label="Pizza crust options (select one)">
          <SelectionGroup.Item data-id="deep-dish">Deep Dish</SelectionGroup.Item>
          <SelectionGroup.Item data-id="thin">Thin</SelectionGroup.Item>
          <SelectionGroup.Item data-id="gluten-free">Gluten Free</SelectionGroup.Item>
        </SelectionGroup.List>
      </FormField.Input>
      <FormField.Hint>Please select a crust to continue</FormField.Hint>
    </FormField>
  );
};

Caution

Select Your Pizza Crust (Select one)

Deep dish is an extra $2.99

import React from 'react';

import {SelectionGroup} from '@workday/canvas-kit-labs-react/selection-group';
import {FormField} from '@workday/canvas-kit-react/form-field';

export default () => {
  const [selected, setSelected] = React.useState(['deep-dish']);

  return (
    <FormField error="caution" as="fieldset">
      <FormField.Label as="legend">Select Your Pizza Crust (Select one)</FormField.Label>
      <FormField.Input
        as={SelectionGroup}
        mode="single"
        selectedIds={selected}
        onSelect={data => setSelected([data.id])}
      >
        <SelectionGroup.List aria-label="Pizza crust options (select one)">
          <SelectionGroup.Item data-id="deep-dish">Deep Dish</SelectionGroup.Item>
          <SelectionGroup.Item data-id="thin">Thin</SelectionGroup.Item>
          <SelectionGroup.Item data-id="gluten-free">Gluten Free</SelectionGroup.Item>
        </SelectionGroup.List>
      </FormField.Input>
      <FormField.Hint>Deep dish is an extra $2.99</FormField.Hint>
    </FormField>
  );
};

Right-to-Left (RTL)

SelectionGroup supports right-to-left languages when specified in the CanvasProvider theme or via the dir attribute on a parent element.

import {SelectionGroup} from '@workday/canvas-kit-labs-react/selection-group';

export default () => (
  <div dir="rtl">
    <SelectionGroup mode="single" initialSelectedIds={['option-b']}>
      <SelectionGroup.List aria-label="اختر واحدا">
        <SelectionGroup.Item data-id="option-a">الخيار أ</SelectionGroup.Item>
        <SelectionGroup.Item data-id="option-b">الخيار ب</SelectionGroup.Item>
        <SelectionGroup.Item data-id="option-c">الخيار ج</SelectionGroup.Item>
      </SelectionGroup.List>
    </SelectionGroup>
  </div>
);

Accessibility

  • Single-select groups render role="radiogroup" with role="radio" items and aria-checked, and follow the radio group keyboard pattern : one tab stop for the group, with arrow keys moving focus and selection together.
  • Multi-select groups render role="group" with role="checkbox" items and aria-checked, and follow the checkbox keyboard pattern : every item is in the tab order and Space toggles it.
  • The icon always renders, in both modes and in both selected states. Selection is communicated by the checkmark inside the icon and a color change, never by color alone.
  • aria-invalid is only set on a single-select group. The group role that a multi-select group renders does not allow the attribute, so a multi-select group communicates its error state through the text referenced by aria-describedby. Make sure that text names the problem.
  • A group label is required. Provide it with aria-label or aria-labelledby on SelectionGroup.List, or wrap the group in a FormField with a visible label. The label must state the selection mode — “Select one” versus “Select all that apply” — so the mode is clear before interacting.

Component API

SelectionGroup

SelectionGroup is a container component that allows users to select one or more options from a predefined list using chip-style controls with a lightweight glyph indicator. It is responsible for creating a and sharing it with its subcomponents using React context. It does not represent a real element.

<SelectionGroup mode="single"> <SelectionGroup.List aria-label="Select one option"> <SelectionGroup.Item data-id="first">First</SelectionGroup.Item> <SelectionGroup.Item data-id="second">Second</SelectionGroup.Item> </SelectionGroup.List> </SelectionGroup>

Alternatively, you may pass in a model using the hoisted model pattern.

const model = useSelectionGroupModel({mode: 'multiple'}); <SelectionGroup model={model}>{Child components}</SelectionGroup>

Props

Props extend from . If a model is passed, props from SelectionGroupModelConfig are ignored.

NameTypeDescriptionDefault
childrenReactNode
model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

SelectionGroup.List

SelectionGroup.List is a container for subcomponents. It renders a radiogroup in single mode and a group in multiple mode, and carries the group's accessible label and notification state.

<SelectionGroup.List aria-label="Select all that apply"> {SelectionGroup.Items} </SelectionGroup.List>

Props

Props extend from div. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
aria-labelstring

Accessible label for the group. The label should state the selection mode, for example "Select one" or "Select all that apply", so the mode is clear before interacting. Required unless aria-labelledby is provided or the group is wrapped in a FormField with a visible label.

aria-labelledbystring

ID of an element that labels the group. Use when the group label is rendered outside the list.

children ((item: T) => ReactNode) | ReactNode
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use instead.

import {handleCsProp} from '@workday/canvas-kit-styling'; import {mergeStyles} from '@workday/canvas-kit-react/layout'; // ... // `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same // function signature, but adds support for style props. return ( <Element {...handleCsProp(elemProps, [ myStyles, myModifiers({ size: 'medium' }), myVars({ backgroundColor: 'red' }) ])} > {children} </Element> )
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

div
refReact.Ref<R = div>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

SelectionGroup.Item

SelectionGroup.Item is a button element with a glyph and a label. Each item requires a data-id prop to handle selection events properly.

<SelectionGroup.Item data-id="option-a">Option A</SelectionGroup.Item>

Props

Props extend from button. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
indexnumber

Optionally pass index to the item. This should be done if SelectionGroup.Item components were created via a map function. This index will ensure keyboard navigation works even if items are inserted out of order.

childrenReactNode

The contents of the item. This is text used as the accessible name of the button for screen readers. Labels should be short and concise.

data-idstring

The identifier of the item. This identifier will be used in change events. If this property is not provided, it will default to a string representation of the zero-based index of the item when it was initialized.

idstring

Optional id. If not set, it will inherit the ID passed to the SelectionGroup component and append the index at the end. Only set this for advanced cases.

asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

button
refReact.Ref<R = button>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useSelectionGroupItem

(
  ,
  ,
  unknown {    "kind": "unknown",    "value": "unknown",    "text": "useSelectionGroupItemProps"   },
  ,
  unknown {    "kind": "unknown",    "value": "unknown",    "text": "useSelectionGroupItemDisabled"   }
)

Model

useSelectionGroupModel

useSelectionGroupModel (config: ):