Skip to main content
In progress

DropdownMenuGroup

DropdownMenuGroup groups multiple dropdown menus into a single, cohesive control area, allowing users to make one or more selections in a compact way without overloading the interface.

Code example
import { DropdownMenuGroup } from '@yleisradio/yds-components-react';

<DropdownMenuGroup>
<DropdownMenuGroup.DropdownMenu
id="menu-1"
options={[
{ text: 'Option 1', value: 'opt1' },
{ text: 'Option 2', value: 'opt2' },
]}
value={selectedValue}
onChange={(value) => setSelectedValue(value)}
/>
<DropdownMenuGroup.DropdownMenuMultiSelect
id="menu-2"
options={[
{ text: 'Option A', value: 'optA' },
{ text: 'Option B', value: 'optB' },
]}
value={selectedValues}
onChange={(values) => setSelectedValues(values)}
/>
</DropdownMenuGroup>

Why to use

DropdownMenuGroup brings related selections together into a clear and consistent whole. It reduces visual clutter compared to always-visible button groups while still supporting both single- and multi-select patterns with unified behavior, alignment, and visual styling across the product.

When to use

Use DropdownMenuGroup when users need to make one or more related selections but the options don’t need to be visible all the time.

PatternWhat it doesTypical use cases
Single SelectAllows selecting one option from a dropdown menu.Selecting a single election-related view or setting, such as election type or region.
Multi SelectAllows selecting zero or more options from a dropdown menu.Filtering content by election-related criteria, such as parties, candidate types, or themes.
Do
  • Use when option lists are long, labels are wide, or the UI would become cluttered if everything was always visible.
  • Use it for supportive settings and filters (not the primary state of the view).
  • Use single-select dropdowns for one choice and multi-select dropdowns for filters.
  • Use multiple menus together when selections belong to the same task (e.g., “Category” + “Sort” + “Time range”).
Don't
  • Don’t hide primary view state changes inside menus — use ButtonGroup or tabs instead.
  • Don’t use DropdownMenuGroup for page-level navigation — use NavigationTabs instead.
  • Don’t use it for switching content within a section — use SectionTabs instead.
  • Don’t combine unrelated selections within the same group.
  • Don’t use menus when options should remain visible and directly comparable — use ButtonGroup instead..

Content Guidelines

Menu labels and options must be short, scannable, and comparable. Users should understand what each menu controls and what each option changes without extra explanation.

Do
  • Use clear menu labels that describe the selection category (e.g., “Alue”, “Aikaväli”, “Järjestys”).
  • Keep option labels short and parallel (e.g., “Uusin”, “Suosituin”, “A–Ö”).
  • Use nouns for filters and categories; use verbs only when an item triggers an immediate action.
  • Use separators to group options when the list includes multiple logical sections.
  • Keep terminology and capitalization consistent within the same menu and across menus in the group
Don't
  • Don’t use long sentences or instructions as menu options.
  • Don’t use vague labels like “Oletus” or “Valitse” without context.
  • Don’t mix verbs and nouns in the same option list without a clear reason.
  • Don’t rely on placeholder text alone to explain scope or rules — use surrounding UI text or helper text.
  • Don’t create deep or overly complex menu structures — split into multiple menus or rethink the model.

Anatomy

DropdownMenuGroup anatomy

  1. Group Container – Wraps multiple menus into a single unit and manages spacing, alignment, and wrapping.
  2. Menu Button – The control users interact with to open and close a menu.
  3. Menu (Panel & Items) – The dropdown (list, radio or checkbox) surface containing selectable options, optional separators, and selection styles.
  4. Label – Text inside each menu button describing the menu.
  5. Selection Indicator – Visual indication of selection

Key DropdownMenuGroup Props

Use these props to configure the DropdownMenuGroup component.

children

DropdownMenu components to display in the group.

TypeExampleDescription
React.ReactNode
DropdownMenu sub-components
Code example
<DropdownMenuGroup>
<DropdownMenuGroup.DropdownMenu id="menu-1" options={[{ text: 'Option 1', value: 'opt1' }, { text: 'Option 2', value: 'opt2' }]} value={undefined} onChange={() => {}}>Menu 1</DropdownMenuGroup.DropdownMenu>
<DropdownMenuGroup.DropdownMenu id="menu-2" options={[{ text: 'Option 1', value: 'opt1' }, { text: 'Option 2', value: 'opt2' }]} value={undefined} onChange={() => {}}>Menu 2</DropdownMenuGroup.DropdownMenu>
</DropdownMenuGroup>

variant

Visual style variant of the buttons.

ValueExampleDescription
primary
Primary button style (default)
text
Low-emphasis text style for lighter controls
Code example
<DropdownMenuGroup variant="primary">
<DropdownMenuGroup.DropdownMenu id="menu-1" options={[]} value={undefined} onChange={() => {}}>Menu 1</DropdownMenuGroup.DropdownMenu>
</DropdownMenuGroup>

size

Size of the buttons.

ValueExampleDescription
xs
Extra small size
sm
Small size
md
Medium size (default)
lg
Large size
Code example
<DropdownMenuGroup size="sm">
<DropdownMenuGroup.DropdownMenu id="menu-1" options={[]} value={undefined} onChange={() => {}}>Menu 1</DropdownMenuGroup.DropdownMenu>
</DropdownMenuGroup>

Size of the menu items.

ValueExampleDescription
sm
Small menu items
md
Medium menu items (default)
Code example
<DropdownMenuGroup menuSize="sm">
<DropdownMenuGroup.DropdownMenu id="menu-1" options={[]} value={undefined} onChange={() => {}}>Menu 1</DropdownMenuGroup.DropdownMenu>
</DropdownMenuGroup>

Key DropdownMenuGroup.DropdownMenu Props

Use these props to configure the single-select dropdown menu.

options

Array of menu options and separators.

TypeExampleDescription
(DropdownMenuOption<T> | DropdownMenuSeparator)[]Array of options and separators
Code example
<DropdownMenuGroup.DropdownMenu
id="menu-1"
options={[
{ text: 'Option 1', value: 'opt1' },
{ text: 'Option 2', value: 'opt2' },
{ type: 'separator' },
{ text: 'Option 3', value: 'opt3' },
]}
value={value}
onChange={onChange}
>
Menu 1
</DropdownMenuGroup.DropdownMenu>

value

Currently selected value (single value).

TypeExampleDescription
T | undefinedSelected option value
Code example
<DropdownMenuGroup.DropdownMenu
id="menu-1"
value="opt1"
options={[{ text: 'Option 1', value: 'opt1' }, { text: 'Option 2', value: 'opt2' }]}
onChange={onChange}
>
Menu 1
</DropdownMenuGroup.DropdownMenu>

defaultValue

Initial selected value for uncontrolled component.

TypeExampleDescription
T | undefinedInitial selected value
Code example
<DropdownMenuGroup.DropdownMenu
id="menu-1"
defaultValue="opt1"
options={[{ text: 'Option 1', value: 'opt1' }, { text: 'Option 2', value: 'opt2' }]}
onChange={onChange}
>
Menu 1
</DropdownMenuGroup.DropdownMenu>

Visual variant of menu items.

ValueExampleDescription
defaultDefault menu item style
radioRadio button style menu items
Code example
<DropdownMenuGroup.DropdownMenu
id="menu-1"
menuItemVariant="radio"
options={options}
value={value}
onChange={onChange}
>
Menu 1
</DropdownMenuGroup.DropdownMenu>

alignMenu

Horizontal alignment of the menu relative to the button.

ValueExampleDescription
leftAligns menu to the left
rightAligns menu to the right (default)
Code example
<DropdownMenuGroup.DropdownMenu
id="menu-1"
alignMenu="left"
options={[options]}
value={value}
onChange={onChange}
>
Menu 1
</DropdownMenuGroup.DropdownMenu>

Key DropdownMenuGroup.DropdownMenuMultiSelect Props

Use these props to configure the multi-select dropdown menu.

options

Array of menu options and separators.

TypeExampleDescription
(DropdownMenuOption<T> | DropdownMenuSeparator)[]Array of options and separators
Code example
<DropdownMenuGroup.DropdownMenuMultiSelect
id="menu-1"
options={[
{ text: 'Option 1', value: 'opt1' },
{ text: 'Option 2', value: 'opt2' },
{ type: 'separator' },
{ text: 'Option 3', value: 'opt3' },
]}
value={values}
onChange={onChange}
>
Menu 1
</DropdownMenuGroup.DropdownMenuMultiSelect>

value

Currently selected values (array).

TypeExampleDescription
T[] | undefinedSelected option values
Code example
<DropdownMenuGroup.DropdownMenuMultiSelect
id="menu-1"
value={['opt1', 'opt2']}
options={[{ text: 'Option 1', value: 'opt1' }, { text: 'Option 2', value: 'opt2' }, { type: 'separator' }, { text: 'Option 3', value: 'opt3' }]}
onChange={onChange}
>
Menu 1
</DropdownMenuGroup.DropdownMenuMultiSelect>

defaultValue

Initial selected values for uncontrolled component.

TypeExampleDescription
T[] | undefinedInitial selected values
Code example
<DropdownMenuGroup.DropdownMenuMultiSelect
id="menu-1"
defaultValue={['opt1', 'opt2']}
options={[{ text: 'Option 1', value: 'opt1' }, { text: 'Option 2', value: 'opt2' }]}
onChange={onChange}
>
Menu 1
</DropdownMenuGroup.DropdownMenuMultiSelect>

Visual variant of menu items.

ValueExampleDescription
defaultDefault menu item style
checkboxCheckbox style menu items
Code example
<DropdownMenuGroup.DropdownMenuMultiSelect
id="menu-1"
menuItemVariant="checkbox"
options={options}
value={values}
onChange={onChange}
>
Menu 1
</DropdownMenuGroup.DropdownMenuMultiSelect>

alignMenu

Horizontal alignment of the menu relative to the button.

ValueExampleDescription
leftAligns menu to the left
rightAligns menu to the right (default)
Code example
<DropdownMenuGroup.DropdownMenuMultiSelect
id="menu-1"
alignMenu="left"
options={options}
value={values}
onChange={onChange}
>
Menu 1
</DropdownMenuGroup.DropdownMenuMultiSelect>

Behavior

  • Menus open from their button and close on selection, outside click, or cancellation.
  • Single-select updates one value; multi-select toggles options independently.
  • Menus in the same group align visually and can wrap onto multiple rows when space is limited.
  • Use alignMenu near edges to prevent the panel from being clipped.
  • onOpen / onClose can be used for analytics and focus management.
  • openMenuOnClick defines whether the menu opens on click (default: true).

Accessibility

  • Ensure full keyboard support (Tab, Arrow keys, Enter, Space).
    (WCAG 2.1.1 — Keyboard)
  • Provide clear context for each menu through visible text or an accessible programmatic name.
    (WCAG 4.1.2 — Name, Role, Value)
  • Keep selection state perceivable (radio/checkbox variants help communicate single vs multi selection).
    (WCAG 1.3.1 — Info and Relationships)

Implementation Examples

Search filters with single select menus

Code example
<div style={{ display: 'flex', gap: '8px', flexDirection: 'column' }}>
<SearchInput
id="search-basic"
label="Hae"
placeholder="Hae"
name="q"
submitButton={<SearchSubmitButton aria-label="Hae" />}
iconClear={{ componentFn: CloseSmall, ariaLabel: 'Tyhjennä haku', onClick: () => {} }}
/>
<DropdownMenuGroup size="sm">
<DropdownMenuGroup.DropdownMenu
id="menu-1"
options={[
{ text: 'Uutiset', value: 'uutiset' },
{ text: 'Urheilu', value: 'urheilu' },
{ text: 'Oppiminen', value: 'oppiminen' },
{ text: 'Elävä arkisto', value: 'elava-arkisto' },
{ text: 'YleX ', value: 'ylex' },
]}
value={undefined}
onChange={(value) => {}}
>
<span>Valitse palvelu</span>
</DropdownMenuGroup.DropdownMenu>
<DropdownMenuGroup.DropdownMenu
id="menu-2"
options={[
{ text: 'Tänään', value: 'tanaan' },
{ text: 'Viikon sisällä', value: 'viikon-sisalla' },
{ text: 'Kuukauden sisällä', value: 'kuukauden-sisalla' },
]}
value={undefined}
onChange={(value) => {}}
>
<span>Valitse aikaväli</span>
</DropdownMenuGroup.DropdownMenu>
<DropdownMenuGroup.DropdownMenu
id="menu-3"
options={[
{ text: 'Suomi', value: 'suomi' },
{ text: 'Svenska', value: 'svenska' },
{ text: 'English', value: 'english' },
]}
value={undefined}
onChange={(value) => {}}
>
<span>Valitse kieli</span>
</DropdownMenuGroup.DropdownMenu>
</DropdownMenuGroup>
</div>

Vaalikone filters with multi-select menus

Code example
import { DropdownMenuGroup, TagFilter } from '@yleisradio/yds-components-react';
import { useState } from 'react';

const MENU_CONFIG = [
{
id: 'puolue-menu',
label: 'Puolue',
options: [
{ text: 'ASYL', value: 'asyl' },
{ text: 'EOP', value: 'eop' },
{ text: 'KD', value: 'kd' },
{ text: 'Kesk.', value: 'kesk' },
{ text: 'Kok.', value: 'kok' },
{ text: 'Lib.', value: 'lib' },
{ text: 'Liik.', value: 'liik' },
{ text: 'PS', value: 'ps' },
{ text: 'RKP', value: 'rkp' },
{ text: 'SDP', value: 'sdp' },
{ text: 'Sit', value: 'sit' },
{ text: 'Vas.', value: 'vas' },
{ text: 'Vihr.', value: 'vihr' },
{ text: 'VL', value: 'vl' },
{ text: 'YMPYL', value: 'ympyl' },
],
},
{
id: 'ika-menu',
label: 'Ikä',
options: [
{ text: 'Alle 30-vuotiaat', value: 'ika-alle-30' },
{ text: '30–50-vuotiaat', value: 'ika-30-50' },
{ text: 'Yli 50-vuotiaat', value: 'ika-yli-50' },
],
},
{
id: 'sukupuoli-menu',
label: 'Sukupuoli',
options: [
{ text: 'Nainen', value: 'nainen' },
{ text: 'Mies', value: 'mies' },
{ text: 'Muu', value: 'muu' },
],
},
{
id: 'aidinkieli-menu',
label: 'Äidinkieli',
options: [
{ text: 'Suomi', value: 'suomi' },
{ text: 'Ruotsi', value: 'ruotsi' },
{ text: 'Jokin muu', value: 'jokin-muu' },
],
},
{
id: 'koulutus-menu',
label: 'Koulutus',
options: [
{ text: 'Alempi korkeakoulututkinto', value: 'alempi-korkeakoulututkinto' },
{ text: 'Ammattitutkinto', value: 'ammattitutkinto' },
{ text: 'Jokin muu', value: 'jokin-muu' },
{ text: 'Peruskoulu', value: 'peruskoulu' },
{ text: 'Ylempi korkeakoulututkinto', value: 'ylempi-korkeakoulututkinto' },
{ text: 'Ylioppilas', value: 'ylioppilas' },
],
},
];

export const ElectionDropdownMenuGroup = () => {
const [selectedValues, setSelectedValues] = useState<Record<string, string[]>>({});

return (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<DropdownMenuGroup>
{MENU_CONFIG.map((menu) => (
<DropdownMenuGroup.DropdownMenuMultiSelect
key={menu.id}
id={menu.id}
options={menu.options}
value={selectedValues[menu.id] || []}
onChange={(value, id) => {
setSelectedValues((selectedValues) => ({ ...selectedValues, [id]: value }));
}}
>
<span>{menu.label}</span>
</DropdownMenuGroup.DropdownMenuMultiSelect>
))}
</DropdownMenuGroup>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', marginTop: '16px' }}>
{Object.entries(selectedValues).flatMap(([menuId, values]) =>
Array.isArray(values)
? values.map((value) => {
const menu = MENU_CONFIG.find((menu) => menu.id === menuId);
const option = menu?.options.find((option) => option.value === value);
const text = option?.text || value;

return (
<TagFilter
key={`${menuId}-${value}`}
size="md"
text={text}
onClose={() => {
setSelectedValues((prev) => {
const currentValues = prev[menuId] || [];
const newValues = currentValues.filter((v) => v !== value);
if (newValues.length === 0) {
const rest = { ...prev };
delete rest[menuId];
return rest;
}
return { ...prev, [menuId]: newValues };
});
}}
/>
);
})
: []
)}
</div>
</div>
);
};

API Reference

The DropdownMenuGroup component accepts all standard HTML <div> attributes in addition to the following props:

PropTypeRequiredDefaultDescription
childrenReact.ReactNodeYesDropdownMenu sub-components
variant'primary' | 'text'No'primary'Visual style variant
size'xs' | 'sm' | 'md' | 'lg'No'md'Size of the buttons
menuSize'sm' | 'md'No'md'Size of menu items
PropTypeRequiredDefaultDescription
idstringYesUnique identifier for the button
options(DropdownMenuOption<T> | DropdownMenuSeparator)[]YesArray of menu options
valueT | undefinedNoSelected value (controlled)
defaultValueT | undefinedNoInitial selected value (uncontrolled)
onChange(value: T, id: string) => voidNoCallback when selection changes
menuItemVariant'default' | 'radio'No'default'Visual variant of menu items
alignMenu'left' | 'right'No'right'Horizontal alignment of menu
onOpen() => voidNoCallback when menu opens
onClose() => voidNoCallback when menu closes
openMenuOnClickbooleanNotrueWhether clicking opens menu
PropTypeRequiredDefaultDescription
idstringYesUnique identifier for the button
options(DropdownMenuOption<T> | DropdownMenuSeparator)[]YesArray of menu options
valueT[] | undefinedNoSelected values (controlled)
defaultValueT[] | undefinedNoInitial selected values (uncontrolled)
onChange(value: T[], id: string) => voidNoCallback when selection changes
menuItemVariant'default' | 'checkbox'No'default'Visual variant of menu items
alignMenu'left' | 'right'No'right'Horizontal alignment of menu
onOpen() => voidNoCallback when menu opens
onClose() => voidNoCallback when menu closes
openMenuOnClickbooleanNotrueWhether clicking opens menu

Type Definitions

export interface DropdownMenuGroupDSProps {
children: React.ReactNode;
variant?: 'primary' | 'text';
size?: 'xs' | 'sm' | 'md' | 'lg';
menuSize?: 'sm' | 'md';
}

export type DropdownMenuGroupProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> &
DropdownMenuGroupDSProps;

export type DropdownMenuGroupDropdownMenuSingleSelectProps<T> = {
id: string;
options?: (DropdownMenuOption<T> | DropdownMenuSeparator)[];
value?: T;
defaultValue?: T;
onChange?: (value: T, id: string) => void;
menuItemVariant?: 'default' | 'radio';
alignMenu?: 'left' | 'right';
onOpen?: () => void;
onClose?: () => void;
openMenuOnClick?: boolean;
};

export type DropdownMenuGroupDropdownMenuMultiSelectProps<T> = {
id: string;
options?: (DropdownMenuOption<T> | DropdownMenuSeparator)[];
value?: T[];
defaultValue?: T[];
onChange?: (value: T[], id: string) => void;
menuItemVariant?: 'default' | 'checkbox';
alignMenu?: 'left' | 'right';
onOpen?: () => void;
onClose?: () => void;
openMenuOnClick?: boolean;
};
  • DropdownMenu – Standalone dropdown menu component
  • ActionMenu – Base component used internally
  • MenuItem – Used internally for menu items
  • ButtonGroup – Alternative for single-selection groups with form semantics
  • CheckboxGroup – Alternative for multiple selection in forms
  • NavigationTabs – Page-level navigation between major sections
  • SectionTabs – Switching content within a single page or section