# Yle Design System (YDS) — Complete Documentation --- # Get started # For AI agents Coding agents such as Claude Code and Cursor can build with YDS: they read the documentation as Markdown and install the same packages developers do. Agents do best when the project tells them where the docs are, which rules to follow and how the app is set up. This page collects the techniques that make that work, most of them from the YDS Agentic Proto Kit. ## Start from the proto kit The [YDS Agentic Proto Kit](https://github.com/Yleisradio/yds-agentic-proto-kit) is a starter project for prototyping with YDS and AI agents: React, TypeScript and Vite, with everything on this page already in place. - YDS 3.0 set up: tokens on `:root`, cascade layers, components, icons, infographics and custom themes - A copy of the YDS docs in `.yds-docs/` - Agent instructions in `AGENTS.md`, shared by Claude Code and Cursor - Skills for building UI, charts and custom themes ```bash cp .env.example .env.local # then add your GitHub token npm run setup npm run dev ``` The token is the same one developers use: see [Install the packages](#install-the-packages). ## Read the documentation | File | Contents | | --- | --- | | [`/llms.txt`](/llms.txt) | Index of every page, with links to the Markdown versions. Follows the [llms.txt](https://llmstxt.org/) convention. | | [`/llm/…`](/llm/components/Button/Button.md) | One page as Markdown, for example `/llm/components/Button/Button.md` | | [`/llms-full.txt`](/llms-full.txt) | Every page in one file | Start from `llms.txt` and fetch only the pages the task needs. `llms-full.txt` is around 800 KB, more than most agents can hold in context at once. The Markdown versions keep the page content and replace the interactive parts: - **Component previews** become a *Live preview* link to the page on this site. Code examples stay as code blocks. - **Token tables** list the CSS custom property and the JavaScript constant for each token, for example `--yds-spacing-16` and `spacing.SPACING_16`. - **Figma and Storybook links** stay at the top of the page. ## Keep a copy of the docs in the project Agents read local files faster and more reliably than they fetch URLs. A copy in the project also keeps the docs in step with the package versions you use. This downloads every page and the index into `.yds-docs/`: ```bash BASE=https://design-system.yle.fi mkdir -p .yds-docs curl -fsSL "$BASE/llm/README.md" -o .yds-docs/README.md curl -fsSL "$BASE/llms.txt" | grep -o '/llm/[^)]*\.md' | while read -r page; do file=".yds-docs/${page#/llm/}" mkdir -p "$(dirname "$file")" curl -fsSL "$BASE$page" -o "$file" done ``` Run it again when you upgrade YDS packages. ## Write the agent instructions ### One file for every tool Keep the rules in `AGENTS.md` and point the other tools to it, so they never drift apart: - **Claude Code:** `CLAUDE.md` contains a single line, `@AGENTS.md`, which imports the file. - **Cursor:** a rule in `.cursor/rules/` applies to `**/*.tsx` files and refers to `AGENTS.md` for the details. ### Make the agent look things up Without instructions, agents guess component APIs from general React knowledge. Tell the agent to read the docs first, and give it the index so it knows which pages exist: ```md ## YDS documentation index > Prefer retrieval-led reasoning over pre-training-led reasoning for any YDS task. > Read the relevant file from `.yds-docs/` instead of relying on training data. - [Button](.yds-docs/components/Button/Button.md) - … ``` The proto kit fills the index automatically: its docs script copies `.yds-docs/README.md` between the markers and rewrites the links to point into `.yds-docs/`. ### State the rules, with wrong and right examples Short rules with a wrong and a right example work better than long explanations. The rules that matter most: - **Use YDS components** from `@yleisradio/yds-components-react` instead of custom HTML for anything YDS already provides. - **Use tokens, never hardcoded values.** Token values already include units, so don't append `px`. - **Use semantic theme colours.** `var(--yds-color-text-default)` follows the theme; `var(--yds-color-gray-95)` doesn't. Don't import `lightTheme` or `darkTheme` for UI colours either: they're literal hex values. In TypeScript, `defaultTheme.TEXT_DEFAULT` is the typed equivalent of the CSS variable. - **No 2.x patterns.** Don't read `theme.yds` or add a `styled.d.ts` with `yds`, `ydsThemeName` or `ydsThemeProps`; they don't exist in 3.0. - **Icons come from** `@yleisradio/yds-icons-react`. Point the agent to the icon list in the docs. - **Say what to do when YDS has no match:** build it from YDS tokens. The proto kit also allows `lucide-react`, sparingly, for missing icons. - **Translate Figma designs, don't copy them.** Code from the Figma MCP server is a reference: map its components and variables to YDS components and tokens. ```css /* WRONG */ .title { color: #1a1a1a; font-size: 24px; padding: 16px; } /* RIGHT */ .title { color: var(--yds-color-text-default); font-size: var(--yds-typography-default-2xl-bold-font-size); font-weight: var(--yds-typography-default-2xl-bold-font-weight); line-height: var(--yds-typography-default-2xl-bold-line-height); padding: var(--yds-spacing-16); } ``` ### Load detailed workflows only when needed Keep `AGENTS.md` short and move task-specific detail into skills that the agent loads when the task calls for them. The proto kit has three, for both Claude Code and Cursor: | Skill | When it loads | | --- | --- | | `use-yds` | Building a screen, page or component: checking the docs, picking components, styling with CSS Modules, verifying the build | | `use-yds-infographics` | Charts and weather icons: required CSS imports, chart props and data format, the icon catalogue | | `use-yds-theme-custom` | Product or brand themes: generating light and dark themes from two colours with `@yleisradio/yds-theme-custom` | ## Work from Figma designs Connect the [Figma MCP server](https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Figma-MCP-server) to the agent and give it links to Figma frames. The agent then reads the layout, components and variables directly instead of guessing from a screenshot. This works best when the design uses the YDS Figma libraries, because its components and variables map to YDS components and tokens in code. The server returns generic React code, so keep the "translate, don't copy" rule above in the agent instructions. [Prototyping with YDS and AI](./for-designers.md#prototyping-with-yds-and-ai) has the step-by-step version for designers. ## Set the app up the way YDS expects Agents get the YDS 3.0 setup wrong most often in the same few places. Have the project set up correctly from the start, and repeat these points in the instructions: - **Tokens on `:root`:** one CSS file imports the token CSS from `@yleisradio/yds-core`. See [Put tokens on `:root`](../using-yds/development/get-started-with-v3.md#3-put-tokens-on-root). - **Layer order first:** declare `@layer reset, yds, app;` before any component CSS loads, and put app styles in `@layer app`. An unlayered reset strips spacing from components. - **No app-wide theme provider:** `YdsThemeProvider` themes a subtree. Use it only for a nested or custom theme. - **Extend components with `className`:** never target their internal class names or use `!important`. - **Infographics:** import `@yleisradio/yds-infographic-components-react/style.css` and the infographic token CSS, or charts render unstyled. ```css /* global.css, imported first */ @layer reset, yds, app; @layer reset { *, *::before, *::after { box-sizing: border-box; } } ``` ```tsx // main.tsx import './global.css'; import './yds-tokens.css'; import { ToastProvider, ToastNotifications } from '@yleisradio/yds-components-react'; ; ``` Bundler settings, Vite included, are in [Get started with 3.0](../using-yds/development/get-started-with-v3.md#4-let-the-bundler-compile-css-modules). ## Install the packages Agents need the same GitHub Packages access as developers: see [Get access to the packages](./for-developers.md#get-access-to-the-packages). Keep the token where the agent can't read it: - Store the token in `.env.local` and generate `.npmrc` from it with a setup script, as the proto kit does. Both files are gitignored. - List both files in `.claudeignore` and `.cursorignore`, so the agent never reads the token into its context. - In Docker builds, pass the token as a [build secret](https://docs.docker.com/build/building/secrets/) so it isn't stored in an image layer. ## Let the agent check its work - Ask the agent to run the type check and lint after every change. The `use-yds` skill ends with `npm run build` and `npm run lint`. - In Claude Code, allow the project's own scripts (`npm run dev`, `build`, `lint`, `format`) in `.claude/settings.json`, so the agent can run them without asking each time. - Add the dev server to `.claude/launch.json`, so the agent can open the app in a preview and look at the result. # For designers There are two ways to design with YDS. Both use the same components and tokens, so the result stays consistent with Yle's services either way. Choose whichever suits the task, or combine them. - **[Using YDS in Figma](#using-yds-in-figma):** detailed UI design, design reviews and handoff. You work with the YDS Figma libraries and produce a design file. - **[Prototyping with YDS and AI](#prototyping-with-yds-and-ai):** trying out ideas quickly, interactive flows and real data. An AI coding agent builds a working prototype from YDS components. ## Using YDS in Figma The YDS Figma libraries contain the same components, tokens and icons that the code packages implement. A design built from them can be implemented with existing components. ### Enable the libraries 1. Open a design file in Yle's Figma. 2. In the **Assets** panel, open **Libraries**. 3. Enable the libraries you need: - [YDS Foundation](https://www.figma.com/design/U1NzhzFpMpgaIDL7MbbrYE/YDS-Foundation): tokens, light and dark themes, text styles and components. Needed for all UI work. - [YDS Global Colors](https://www.figma.com/design/3ARTAZ2XrJHPq2g8iSBORW/YDS-Global-Colors): global palettes and colour swatches. - [YDS Icons and Logos](https://www.figma.com/design/lSboxfILj3Thr1o145POJv/YDS-Icons-and-Logos): basic icons, illustrative icons, logos and graphical assets. - [YDS Infographics](https://www.figma.com/design/ttv0a97s8ViuxBHtRyn3n2/YDS-Infographics): colour themes, weather symbols and examples of simple infographic components. The Yle Next font is available in Yle's Figma without installing anything. Use the YDS text styles instead of setting font properties by hand. If you can't open the libraries, ask on the [#design-system](https://yle.slack.com/archives/C01KDJ7JX5Y) Slack channel. ### Design with tokens - **Use theme colours for UI.** Theme colours are semantic, grouped by purpose: background, text, action, border, feedback and so on. They have a light and a dark value, so a design built with them works in both themes. Use the global palette only when no theme colour fits. See [Theme colors](../design-tokens/theme-colors.md). - **Space with the spacing scale.** Spacing follows an 8 px rhythm, with 2 px and 4 px for fine adjustments inside components. See [Spacing](../design-tokens/spacing.md). - **Use text styles for typography.** See [Typography](../design-tokens/typography.md). - **Check the themes the service uses.** Yle services use both light and dark themes; Yle Areena uses only the dark theme. ### Accessibility checks YDS colour pairings meet WCAG 2.2 AA contrast when used as documented. When a design combines colours in other ways, check the contrast yourself, for example with a contrast plugin such as Able. Colour-vision simulation, for example with the Color Blind plugin, shows whether information depends on colour alone. ### Handing off to developers - Every component and token page on this site links to its Figma node and Storybook story at the top. - Refer to components by the names used on this site; code uses the same names. - For animation, describe the motion with the transition tokens. See [Motion](../design-principles/motion-language-principles.md) and [Transitions](../design-tokens/transitions.md). ## Prototyping with YDS and AI An AI coding agent such as Claude Code or Cursor can build a working prototype from real YDS components while you describe what you want in plain language. The prototype looks and behaves like a Yle service, runs in the browser, and can use real data. ### Get started 1. Get a copy of the [YDS Agentic Proto Kit](https://github.com/Yleisradio/yds-agentic-proto-kit). It comes with the agent instructions that keep the agent on YDS components and tokens. 2. Create a GitHub token for installing the YDS packages. The proto kit has a [step-by-step guide](https://github.com/Yleisradio/yds-agentic-proto-kit/blob/main/docs/github-npm-token.md). 3. Copy `.env.example` to `.env.local` and paste the token there yourself. The agent is set up not to read that file. 4. Open the project in your AI coding tool and ask it to run `npm run setup` and start the dev server. [For AI agents](./for-ai-agents.md) explains how the proto kit works and how to set up your own project the same way. ### Start from a Figma design For the best results, connect the [Figma MCP server](https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Figma-MCP-server) to your AI coding tool. The agent can then read a frame straight from Figma, with its layout, components and variables, instead of working from a written description or a screenshot. 1. Set up the Figma MCP server in your AI coding tool. Figma recommends the remote server. 2. Design or sketch the screen with the YDS libraries. Use YDS components and variables rather than detached copies and hand-picked values, so the agent can tell which YDS component and token each part is. 3. Copy the link to the frame and give it to the agent in the proto kit, for example: *"Build this screen with YDS components and tokens:"* followed by the link. The Figma MCP server returns generic React code as a reference. Ask the agent to rebuild the screen with YDS components and tokens instead of copying that code's markup and values. ### Tips for prompting - **Name the components.** "A list of ChoiceButtons with a primary Button below" gets closer than "some options and a submit button". The [Components](../components/index.md) page shows what is available. - **Check both themes** and try the prototype with a keyboard. The components are accessible, but how they are combined is up to the prototype. - **Ask the agent to explain** which components and tokens it used, if you plan to hand the idea off as a design. ## Before designing something new Whichever way you use YDS: 1. Check [Components](../components/index.md) or the YDS Foundation library in Figma. An existing component, or a variant of one, may already cover the need. 2. Check the [project board](https://github.com/orgs/Yleisradio/projects/32) and [open issues](https://github.com/Yleisradio/yle-design-system/issues) to see if what you need is in progress. 3. Think beyond your own service: would other services need this too? Write down the use cases. 4. If something is missing from YDS: Propose it. See [Support and contributing](./support.md). # For developers YDS ships as npm packages for web and React Native, plus static files for native apps. See [Overview](./overview.md#packages) for the full package list. ## Get access to the packages The packages are published to Yle's GitHub Packages registry, so installing them needs a GitHub account with access to the Yleisradio organisation. 1. Point the `@yleisradio` scope to GitHub Packages in the project's `.npmrc`: ```ini @yleisradio:registry=https://npm.pkg.github.com/ ``` 2. Create a [personal access token (classic)](https://github.com/settings/tokens) with the `read:packages` scope. Fine-grained tokens don't work with the npm registry. 3. Authorise the token for the Yleisradio organisation: in the token list, select **Configure SSO** and **Authorize** next to *yleisradio*. Without this step, installs fail with `401 Unauthorized`. 4. [Authenticate npm with the token](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry#authenticating-with-a-personal-access-token), for example in `.npmrc`: ```ini //npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN} ``` Keep the token itself out of version control. 5. Give the same access to every environment that installs packages: CI, deployment builds and AI agent sandboxes. In GitHub Actions: ```yaml - uses: actions/setup-node@v4 with: node-version: '22' registry-url: https://npm.pkg.github.com/ scope: '@yleisradio' - run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` ## Choose your starting point | You're building | Start here | | --- | --- | | A React web app | [Get started with yds-components-react 3.0](../using-yds/development/get-started-with-v3.md) | | A web app without React (Vue, Svelte, plain HTML) | [Without React](../using-yds/development/get-started-with-v3.md#without-react) | | Your own styles with YDS tokens | [Using tokens](../design-tokens/index.md#using-tokens) | | Icons only | [Icons and Logos](../icons-and-logos/index.md#installation) | | A React Native app | [React Native](#react-native) | | A native iOS or Android app | [Native apps](#native-apps) | | Charts and infographics | [Infographics](#infographics) | Every web setup loads the Yle Next font: see [Load Yle Next](../using-yds/development/get-started-with-v3.md#2-load-yle-next). ## React Native ```bash npm install @yleisradio/yds-core @yleisradio/yds-components-react-native @yleisradio/yds-icons-react-native react-native-svg ``` Wrap the component tree in the theme provider: ```tsx import { YdsThemeProvider, Tag } from '@yleisradio/yds-components-react-native'; Hello world ; ``` Components available for React Native are tagged **React Native** on the [Components](../components/index.md) page. ## Native apps YDS doesn't provide native iOS or Android components, but native apps can use its assets: - **Icons:** archives of basic and illustrative icons, SVG for iOS and VectorDrawable for Android. See [CDN](./overview.md#cdn). - **Colours:** Android colour resources (`colors.xml`, `theme-dark.xml`) are included in `@yleisradio/yds-core` under `tokens/build/android/`. ## Infographics ```bash npm install @yleisradio/yds-core @yleisradio/yds-infographic-components-react @yleisradio/yds-infographic-icons-react ``` `@yleisradio/yds-core` exports the infographic colour themes as `infographicLight`, `infographicDark` and `infographicDefault` (CSS custom properties). ## Contributing code Local setup, commit conventions and the release flow are in the repository [README](https://github.com/Yleisradio/yle-design-system#development). Before opening a pull request for anything larger than a fix, see [Support and contributing](./support.md). # Introduction Yle Design System (YDS) is the shared foundation for Yle's digital services. Designers and developers get the same building blocks — design tokens, components, icons and guidelines — so services stay consistent, accessible and on-brand without every team solving the same problems again. ## Why YDS exists - **A shared language.** Designers and developers use the same names for the same things, in Figma and in code. - **Build once, use everywhere.** Common UI solutions are built and tested once and reused across services, so teams can focus on what is new. - **Accessibility and brand built in.** Components and tokens carry Yle's visual identity and accessibility requirements, so services don't have to reinvent them. ## What YDS covers - **Design tokens:** colours and themes, typography, spacing, border radius, borders, shadows and transitions. - **Components:** accessible, generic UI building blocks such as buttons, form controls, notifications, dialogs and navigation, for React and React Native, with matching Figma components. - **Icons and logos:** basic icons, illustrative icons, Yle logos and graphical assets such as age-rating badges. - **Infographics:** colour themes, icons and chart components for data visualisation. - **Guidelines:** usage, content and accessibility guidance for each component, plus design principles such as motion. ## What YDS doesn't cover - **Service-specific components and layouts.** YDS components are deliberately generic building blocks. Services compose them into their own views, and components that only one service needs stay in that service. - **Native iOS and Android components.** Native apps can use YDS icons and colour tokens, but YDS doesn't provide native UI components. - **Brand guidelines beyond digital UI.** Marketing and other non-digital touchpoints follow Yle's brand guidelines. ## Who it's for - **Designers** work with the Figma libraries or prototype with AI agents. See [For designers](./for-designers.md). - **Developers** use the npm packages. See [For developers](./for-developers.md). - **Everyone building Yle's services**, product owners and editors included, can use these docs to see what is available and how it is meant to be used. - **AI agents** read the documentation as plain Markdown and install the same packages. See [For AI agents](./for-ai-agents.md). ## Accessibility YDS aims to meet [WCAG 2.2](https://www.w3.org/TR/WCAG22/) level AA in every component and token pairing. Components handle keyboard interaction, focus, semantics and colour contrast. Services remain responsible for their content, page structure and how components are combined. Each component page has an Accessibility section that lists what the component provides. ## How YDS is maintained Yle's design system team maintains YDS. Designers and developers from Yle's services contribute to it, and new components start from needs in the services. See [Support and contributing](./support.md) for how to ask, report and propose. ## Next steps - [Overview](./overview.md): what the system consists of and where to find each part. - [For designers](./for-designers.md) - [For developers](./for-developers.md) - [For AI agents](./for-ai-agents.md) - [Components](../components/index.md) # Overview YDS is made of a few parts that share the same tokens and names. Design tokens start as Figma variables and are built into code formats in `@yleisradio/yds-core`, so a colour or spacing value means the same thing in a design file and in an application. | Part | Used for | Where | | --- | --- | --- | | Documentation | Guidelines, usage, API reference | This site | | Figma libraries | Designing with components, tokens and icons | [YDS Foundation](https://www.figma.com/design/U1NzhzFpMpgaIDL7MbbrYE/YDS-Foundation) and others, see below | | npm packages | Building with tokens, components, icons and fonts | Yle's GitHub Packages registry, see below | | Storybook | Trying out components and their props | [Storybook](https://ds-storybook.test.yle.fi/) | | CDN | Fonts and native icon files without installing packages | `design-system.cdn.yle.fi`, see below | | Source code | Packages, docs and Storybook | [GitHub](https://github.com/Yleisradio/yle-design-system) | ## What each part contains ```mermaid block-beta columns 4 hFigma["Figma libraries"] hRepo["YDS repository"]:2 hDelivery["Delivery"] foundation["YDS Foundation
tokens, themes,
text styles, components"] core["yds-core
tokens and themes
all"] themeCustom["yds-theme-custom
custom themes
all"] storybook["Storybook
interactive
component examples"] globalColors["YDS Global Colors
global palettes,
colour swatches"] icons["yds-icons
UI icons
all react react native"] infographicIcons["yds-infographic-icons
weather icons
all react react native"] npm["npm packages
GitHub Packages
registry"] infographics["YDS Infographics
colour themes, weather
symbols, chart examples"] componentsReact["yds-components
UI components
react"] componentsNative["yds-components
UI components
react native"] cdn["CDN
fonts and icons
for native apps"] iconsLogos["YDS Icons and Logos
icons, illustrative icons,
logos, graphical assets"] infographicComponents["yds-infographic-
components

charts react"] fonts["yds-fonts
Yle Next font files
all"] site["Docs site
Markdown and
llms.txt for agents"] space docs["Documentation
guidelines, usage, API reference"]:2 space classDef header fill:none,stroke:none,font-weight:bold classDef prerelease stroke-dasharray:5 4 class hFigma,hRepo,hDelivery header class themeCustom,componentsNative,infographicComponents prerelease ``` - **Tags** show the platform packages: all works anywhere (CSS, JavaScript, SVG); react and react native are separate packages with those suffixes. For example, `yds-components` react is `@yleisradio/yds-components-react`. - **Dotted boxes** are 0.x packages: usable, but their APIs may still change between minor versions. - **In the diagram below**, `yds-core` stands for the token and theme packages, `yds-icons` for the icon packages and `yds-components` for the component packages. ## How the parts connect ```mermaid %%{init: {"flowchart": {"nodeSpacing": 14, "rankSpacing": 40}}}%% flowchart LR hFigma[Figma libraries]:::header ~~~ hRepo[YDS repository]:::header ~~~ hDelivery[Delivery]:::header ~~~ hUse[Using YDS]:::header foundation[YDS Foundation] globalColors[YDS Global Colors] infographics[YDS Infographics] iconsLogos[YDS Icons and Logos] components[yds-components] tokens[yds-core] icons[yds-icons] docsSource[Documentation] storybook[Storybook] npm[npm packages] cdn[CDN] site[Docs site and llms.txt] services[Yle services] prototypes[AI prototypes] foundation --> components foundation & globalColors & infographics --> tokens infographics & iconsLogos --> icons iconsLogos ~~~ docsSource components --> storybook & npm tokens & icons --> npm & cdn docsSource --> site npm --> services & prototypes cdn --> services site -.-> services & prototypes classDef header fill:none,stroke:none,font-weight:bold ``` - **Tokens** are defined as Figma variables and built into CSS, JavaScript, SCSS, Less and Android formats in `@yleisradio/yds-core`. - **Icons** are drawn in Figma, exported as SVG and generated into the icon packages for web, React and React Native. - **Components** are built on the tokens and icons, shown in Storybook and released as npm packages together with the token and icon packages. - **Services and prototypes** install the packages and follow the documentation. What they learn comes back as feedback and contributions to the Figma libraries and the code. ## Figma libraries | Library | Contents | | --- | --- | | [YDS Foundation](https://www.figma.com/design/U1NzhzFpMpgaIDL7MbbrYE/YDS-Foundation) | Design tokens as Figma variables, light and dark themes, text styles and shared components | | [YDS Global Colors](https://www.figma.com/design/3ARTAZ2XrJHPq2g8iSBORW/YDS-Global-Colors) | Global palettes and colour swatches | | [YDS Icons and Logos](https://www.figma.com/design/lSboxfILj3Thr1o145POJv/YDS-Icons-and-Logos) | Basic icons, illustrative icons, logos and graphical assets | | [YDS Infographics](https://www.figma.com/design/ttv0a97s8ViuxBHtRyn3n2/YDS-Infographics) | Infographic colour themes, weather symbols and examples of simple infographic components | How to enable them: [Using YDS in Figma](./for-designers.md#using-yds-in-figma). ## Packages All packages are published under the `@yleisradio` scope to Yle's GitHub Packages registry. Installing them requires access: see [For developers](./for-developers.md#get-access-to-the-packages). | Package | Contents | Platform | | --- | --- | --- | | `@yleisradio/yds-core` | Design tokens and themes as CSS custom properties, JS/TS constants, SCSS, Less and Android colour resources | All | | `@yleisradio/yds-fonts` | Yle Next font files | All | | `@yleisradio/yds-components-react` | React components | Web | | `@yleisradio/yds-components-react-native` | React Native components | Mobile | | `@yleisradio/yds-icons` | Icon SVG files and icon name types | All | | `@yleisradio/yds-icons-react` | Icons as React components | Web | | `@yleisradio/yds-icons-react-native` | Icons as React Native components | Mobile | | `@yleisradio/yds-theme-custom` | Generating accessible custom themes from a colour combination | Web | | `@yleisradio/yds-infographic-components-react` | Column, line, area and donut charts, legends and accessible data tables | Web | | `@yleisradio/yds-infographic-icons` | Infographic icon SVG files, such as weather symbols | All | | `@yleisradio/yds-infographic-icons-react` | Infographic icons as React components | Web | | `@yleisradio/yds-infographic-icons-react-native` | Infographic icons as React Native components | Mobile | Release notes for every package are in [GitHub releases](https://github.com/Yleisradio/yle-design-system/releases). ## Storybook [Storybook](https://ds-storybook.test.yle.fi/) shows every React component in isolation, with controls for trying out props and states. Component pages on this site link to the matching story. React Native components have their own Storybook in the [YDS repository](https://github.com/Yleisradio/yle-design-system). It isn't published on the web: run it locally on an iOS or Android simulator with `yarn storybook:native`. See the repository README for setup. ## CDN `design-system.cdn.yle.fi` serves static files that don't need a package install: - **Fonts:** [`fonts.css`](https://design-system.cdn.yle.fi/fonts/fonts.css) loads Yle Next with `@font-face` rules. See [Load Yle Next](../using-yds/development/get-started-with-v3.md#2-load-yle-next). - **iOS icons (SVG):** [basic icons](https://design-system.cdn.yle.fi/icons/ios/icons.tar.gz), [illustrative icons](https://design-system.cdn.yle.fi/icons/ios/illustrative-icons.tar.gz) - **Android icons (VectorDrawable):** [basic icons](https://design-system.cdn.yle.fi/icons/android/icons.tar.gz), [illustrative icons](https://design-system.cdn.yle.fi/icons/android/illustrative-icons.tar.gz) # Support and contributing | I want to… | Where | | --- | --- | | Ask a question or get help | [#design-system](https://yle.slack.com/archives/C01KDJ7JX5Y) on Slack | | Discuss about technical issue | [#ds-kehitys](https://yle.slack.com/archives/C02G2TP79HB) on Slack | | Report a bug | [New issue](https://github.com/Yleisradio/yle-design-system/issues/new/choose) → **Bug report** | | Suggest a change to an existing component, token or icon | [New issue](https://github.com/Yleisradio/yle-design-system/issues/new/choose) → **Change request** | | Propose a new component or feature | [New issue](https://github.com/Yleisradio/yle-design-system/issues/new/choose) → **New feature** | | Explore an early design idea | [New issue](https://github.com/Yleisradio/yle-design-system/issues/new/choose) → **Concept design** | | See what is being worked on | [Project board](https://github.com/orgs/Yleisradio/projects/32) | | See what has changed | [GitHub releases](https://github.com/Yleisradio/yle-design-system/releases) | | Fix or improve a docs page | **Edit this page** link at the top of the page | Release announcements are also posted on #design-system. ## Before you ask Search these docs, [Storybook](https://ds-storybook.test.yle.fi/) and [existing issues](https://github.com/Yleisradio/yle-design-system/issues?q=is%3Aissue), open and closed. Someone may have asked already. ## Reporting a bug A good bug report includes: - the package and its version - where it happens: browser, device, React or React Native version - steps to reproduce, with a code sample or a link if possible - what you expected and what happened instead. Don't put security-sensitive details in an issue. Contact the team on Slack and they'll point you to a private channel. ## Proposing a change or a new component Open an issue before you start designing or building. The team checks that the proposal fits the system: - Does more than one service need it, or could it be generalised so that they would? - Could an existing component be extended instead? - Is it accessible and built on existing design tokens? - Does it work in both light and dark themes? Once the proposal is agreed, the design is done in the YDS Figma libraries and the implementation as a pull request. ## Contributing code Pull requests are welcome. Before opening one: - Set up the repository with the [README](https://github.com/Yleisradio/yle-design-system#development) instructions. - Write commit messages as [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). - Add a [changeset](https://github.com/Yleisradio/yle-design-system/blob/main/.changeset/README.md) for every change that should release a new package version. - Document new components with the [docs page template](https://github.com/Yleisradio/yle-design-system/blob/main/docs/template.mdx). More detail: [CONTRIBUTING.md](https://github.com/Yleisradio/yle-design-system/blob/main/CONTRIBUTING.md). --- # Components # Accordion An Accordion lets users hide and reveal sections of related content. It helps reduce visual clutter by showing only section titles by default, while allowing users to expand the content they need. - [Figma](https://www.figma.com/design/U1NzhzFpMpgaIDL7MbbrYE/YDS-Foundation?m=auto&node-id=285-1938&t=lI9imRC4H4TmzLE4-1) - [Storybook](https://ds-storybook.test.yle.fi/?path=/docs/components-accordion--docs) - [Edit this page](https://github.com/Yleisradio/yle-design-system/blob/main/docs/components/Accordion/Accordion.mdx) - [Create a new issue](https://github.com/Yleisradio/yle-design-system/issues/new) *[Live preview](https://design-system.yle.fi/docs/components/Accordion/)*
Code example ```tsx import { Accordion, AccordionItem } from '@yleisradio/yds-components-react'; Content for first item Content for second item ```
## Why to use Accordion helps users navigate large amounts of content by grouping related information into collapsible sections. It reduces visual clutter and cognitive load by showing only section titles by default, while still allowing users to access detailed content when needed. Accordions are especially useful when most users need only part of the available information, such as in help content, rules, or frequently asked questions. ## When to use Use an Accordion when content can be divided into independent, optional sections and it is not essential for all content to be visible at once. Accordions work well when users benefit from scanning headings first and choosing what to expand. > **Do** > - Use an Accordion for supplementary or secondary content. > - Use when sections can be read independently, in any order. > - Use for FAQs, instructions, rules, help content, and settings. > - Use when reducing vertical scrolling improves clarity and scannability. > **Don't** > - Don’t hide critical or mandatory information inside an Accordion. > - Don’t use Accordion for navigation between views — use [NavigationTabs](../NavigationTabs/NavigationTabs.md) or [SectionTabs](../SectionTabs/SectionTabs.md) instead. > - Don’t use Accordion when content requires focused attention or user action — use a [Dialog](../Dialog/Dialog.md) ([Modal](../Modal/Modal.md) or [BottomSheet](../BottomSheet/BottomSheet.md)) instead. > - Don’t use Accordion when there is only one short expandable section — use read more buttton/show more button instead. > - Don’t nest Accordions inside other Accordions. ## Content Guidelines Accordion titles are the primary decision point for users. Users decide whether to expand a section based on the title alone, so titles must clearly describe what the content contains. > **Do** > - Use short, descriptive titles that summarize the content (e.g. “Säännöt ja pistejako”, “Palkinnot”, “Mikä Tietäjä on?”). > - Phrase titles in a way that matches the content type — questions work well for FAQs. > - Keep wording parallel across items to support scanning. > - Place the most commonly needed or most important item first. > **Don't** > - Don’t use vague titles like “Lisätietoja” or “Muut”. > - Don’t use long sentences or explanatory text as titles. > - Don’t repeat the same wording across multiple items. > - Don’t rely on the expanded content to explain what the section is about. ## Anatomy ![Accordion anatomy](../../img/accordion-anatomy.svg) 1. **Icon** – Visual indicator that shows whether the section is expanded or collapsed. 2. **Title** – The clickable header that summarizes the content of the section. 3. **Group container** – Wraps the title and content and manages spacing, alignment, borders, and wrapping. 4. **Closed item** – The closed state of the accordion item. 5. **Open item** – The open state of the accordion item. 6. **Content** – The section that contains the detailed content, revealed when the item is expanded. 7. **Border** – Optional visual separator between sections. ## Key Accordion Props Use these props to configure the Accordion component. ### children The AccordionItem components to display. | Type | Example | Description | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | | `React.ReactNode` |
Content 1Content 2
| Child AccordionItem components |
Code example ```tsx Content 1 Content 2 ```
## Key AccordionItem Props Use these props to configure the AccordionItem component. ### title The title text or content displayed in the accordion header. | Type | Example | Description | | ----------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------ | | `React.ReactNode` |
Content
| Title content for the accordion item |
Code example ```tsx Content Bold Title}>Content ```
### variant Visual style variant of the accordion item. | Value | Example | Description | | ---------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | | `default` |
Content
| Default style with border | | `noBorder` |
Content
| Style without border or padding |
Code example ```tsx Content Content ```
### size Size of the accordion item. | Value | Example | Description | | ----- | ---------------------------------------------------------------------------------------------------------------- | ------------------- | | `sm` |
Content
| Small size | | `md` |
Content
| Default medium size | | `lg` |
Content
| Large size |
Code example ```tsx Content Content Content ```
### defaultOpen Whether the accordion item should be open by default. | Type | Example | Description | | --------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------- | | `boolean` |
Content
| Opens the item on initial render |
Code example ```tsx Content Content ```
### isDisabled Whether the accordion item is disabled. | Type | Example | Description | | --------- | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | `boolean` |
Content
| Disables interaction with the accordion item |
Code example ```tsx Content ```
### removePadding Removes padding from the accordion content. | Type | Example | Description | | --------- | -------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | `boolean` |
Content
| Removes padding from content area |
Code example ```tsx Content ```
### contentProps Additional props for the content container. | Type | Example | Description | | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | | `{ padding?: 'default' \| 'horizontal' \| 'vertical' \| 'none' }` |
ContentContentContent 3Content 4
| Custom padding configuration for content |
Code example ```tsx Content Content Content Content ```
## Behavior - Each accordion item expands and collapses independently. - Multiple items can be open at the same time. - Clicking the title toggles the expanded state. - The icon (chevron) rotates or switches to reflect expanded vs collapsed state. ## Accessibility - Provide a unique id on each AccordionItem for proper labeling and control linkage. - Accordion headers must be focusable and keyboard-activatable so users can expand and collapse items with the keyboard (for example, using Enter or Space).
([WCAG 2.1.1 — Keyboard](https://www.w3.org/WAI/WCAG22/Understanding/keyboard)) - Ensure both the header and the panel have an accessible name by using clear, descriptive titles and avoiding empty panels.
([WCAG 2.4.6 — Headings and Labels](https://www.w3.org/WAI/WCAG22/Understanding/headings-and-labels)) ## Implementation examples ### FAQ Use an Accordion for frequently asked questions so users can expand only the answers they need. *[Live preview](https://design-system.yle.fi/docs/components/Accordion/)*
Code example ```tsx Tilauksen voit peruuttaa Asetukset-sivulta tai ottamalla yhteyttä asiakaspalveluun. Toimitus tapahtuu 1–3 arkipäivän kuluessa tilauksesta. Kyllä, maksutavan voit vaihtaa tilin asetuksista milloin tahansa. ```
### One item open by default Use `defaultOpen` to show one section expanded on first load so users see sample content without clicking. *[Live preview](https://design-system.yle.fi/docs/components/Accordion/)*
Code example ```tsx Content for first item Content for second item (open by default) Content for third item ```
## API Reference ### Accordion Props The Accordion component accepts all standard HTML `
` attributes in addition to the following props: | Prop | Type | Required | Default | Description | | ---------- | ----------------- | -------- | ------- | ----------------------------------- | | `children` | `React.ReactNode` | No | — | AccordionItem components to display | ### AccordionItem Props The AccordionItem component accepts all standard HTML `` attributes in addition to the following props: | Prop | Type | Required | Default | Description | | --------------- | ----------------------------------------------------------------- | -------- | ----------- | -------------------------------------- | | `title` | `React.ReactNode` | Yes | — | Title content for the accordion item | | `id` | `string` | No | — | Unique identifier for accessibility | | `variant` | `'default' \| 'noBorder'` | No | `'default'` | Visual style variant | | `size` | `'sm' \| 'md' \| 'lg'` | No | `'md'` | Size of the accordion item | | `defaultOpen` | `boolean` | No | `false` | Whether the item is open by default | | `isDisabled` | `boolean` | No | `false` | Whether the item is disabled | | `removePadding` | `boolean` | No | — | Removes padding from content area | | `contentProps` | `{ padding?: 'default' \| 'horizontal' \| 'vertical' \| 'none' }` | No | `{}` | Additional props for content container | | `children` | `React.ReactNode` | No | — | Content displayed when expanded | ### Type Definitions ```tsx export type AccordionSize = 'sm' | 'md' | 'lg'; export type Padding = 'default' | 'horizontal' | 'vertical' | 'none'; export interface AccordionContentDSProps { padding?: Padding; } export interface AccordionItemDSProps { id?: string; variant?: 'default' | 'noBorder'; title: React.ReactNode; size?: AccordionSize; defaultOpen?: boolean; isDisabled?: boolean; contentProps?: AccordionContentDSProps; children?: React.ReactNode; removePadding?: boolean; } export interface AccordionDSProps { children?: React.ReactNode; } export type AccordionProps = HTMLAttributes & AccordionDSProps; export type AccordionItemProps = HTMLAttributes & AccordionItemDSProps; ``` ## Related Components - [Button](../Button/Button.md) – Can be used inside accordion content - [SectionTabs](../SectionTabs/SectionTabs.md) – Alternative component for organizing content - [NavigationTabs](../NavigationTabs/NavigationTabs.md) – Alternative component for navigation between views - [Dialog](../Dialog/Dialog.md) – Alternative component for showing content in a modal - [BottomSheet](../BottomSheet/BottomSheet.md) – Alternative component for showing content in a bottom sheet import { Link2, Mail, Whatsapp, Facebook, TwitterX, Linkedin, Share, } from '@yleisradio/yds-icons-react'; import { Resources } from 'yds-documentation/src/components/Resources/Resources'; import { ComponentPreview } from 'yds-documentation/src/components/ComponentPreview/ComponentPreview'; # ActionMenu ActionMenu is a button that opens a dropdown list of actions. It groups multiple related actions into a compact control without changing the component’s state when an item is selected. - [Figma](https://www.figma.com/design/U1NzhzFpMpgaIDL7MbbrYE/YDS-Foundation?node-id=8809-10634&p=f&t=U4uSG0M5o85iz0VX-0) - [Storybook](https://ds-storybook.test.yle.fi/?path=/docs/components-actionmenu--docs) - [Edit this page](https://github.com/Yleisradio/yle-design-system/blob/main/docs/components/ActionMenu/ActionMenu.mdx) - [Create a new issue](https://github.com/Yleisradio/yle-design-system/issues/new) *[Live preview](https://design-system.yle.fi/docs/components/ActionMenu/)*
Code example ```tsx import { ActionMenu, MenuItem, MenuSeparator } from '@yleisradio/yds-components-react'; {}}>Option 1 {}}>Option 2 {}}>Option 3 ```
## Why to use ActionMenu provides a clear and space-efficient way to expose multiple related actions from a single entry point. Instead of showing several buttons side by side, ActionMenu keeps the interface uncluttered while still making all actions easily accessible. It is built on top of Menu, ensuring consistent positioning, keyboard navigation, and accessibility across products while supporting typical action-based use cases such as sharing, editing, or managing content. ## When to use Use ActionMenu when the user needs to trigger one of several related actions that do not affect the component’s state. > **Do** > - Use when multiple related actions are available from the same context. > - Use for secondary or contextual actions such as sharing, editing, or managing content. > - Use when space is limited and showing all actions at once would clutter the UI. > **Don't** > - Don’t use for selecting values or changing component state (use [DropdownMenu](../DropdownMenu/DropdownMenu.md) instead). > - Don’t use for navigation between views or pages. > - Don’t use for a single action (use a [Button](../Button/Button.md) instead). ## Content Guidelines ActionMenu items represent actions, not values. > **Do** > - Use verbs for menu items (e.g. “Jaa”, “Muokkaa”, “Poista”). > - Keep labels concise and action-oriented. > - Group related actions using separators when necessary. > **Don't** > - Don’t use nouns or descriptive values as menu items. > - Don’t use vague labels like “OK” or “Kyllä”. > - Don’t mix actions with selection-type options in the same menu. ## Anatomy ![ActionMenu anatomy](../../img/actionmenu-anatomy.svg) 1. **ActionMenu button** – The trigger that opens the list of actions. 2. **Label** – Text describing the category of actions (e.g. “Actions”, “Share”). 3. **Chevron icon** – Indicates that the button opens a dropdown. 4. **Menu surface** – The floating container holding the action items. 5. **MenuItem (action)** – A single actionable option inside the menu. 6. **MenuItem icon (optional)** – An optional leading icon inside a `MenuItem` 7. **MenuSeparator (optional)** – Groups related actions. ## Key ActionMenu Props Use these props to configure the ActionMenu component. ### text The text displayed on the action menu button. | Type | Example | Description | | -------- | ------------------------------------------------------------------------------------------------------------------- | ----------------- | | `string` | {}}>Option | Button label text |
Code example ```tsx {}}>Option ```
### children Menu items to display in the dropdown menu. | Type | Example | Description | | ----------------- | --------------------------------------------------------------------------------------------- | ------------------------------ | | `React.ReactNode` | Item | MenuItem components to display |
Code example ```tsx {}}>Option 1 {}}>Option 2 ```
### dropdown Whether to show a dropdown chevron icon. | Value | Example | Description | | ------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------- | | `true` | {}}>Option | Shows the chevron icon | | `false` | {}}>Option | Hides the chevron icon |
Code example ```tsx {}}>Option {}}>Option ```
### openMenuOnClick Whether clicking the button opens the menu. | Type | Example | Description | | --------- | --------------------------------------------------------------------------------- | -------------------------------- | | `boolean` | | Controls if click opens the menu |
Code example ```tsx {}}>Option ```
### menuSize Size of the menu items. | Value | Example | Description | | ----- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | | `sm` | {}}>Option | Small menu items | | `md` | {}}>Option | Default medium menu items |
Code example ```tsx {}}>Option {}}>Option ```
### alignMenu Horizontal alignment of the menu relative to the button. | Value | Example | Description | | ------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | | `left` | {}}>Option | Aligns menu to the left | | `right` | {}}>Option | Aligns menu to the right (default) |
Code example ```tsx {}}>Option {}}>Option ```
### position Preferred direction in which the menu opens relative to the trigger button. The menu automatically adjusts if it would be clipped by the viewport. | Value | Example | Description | | -------- | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | | `bottom` | {}}>Option | Opens below the button (default) | | `top` | {}}>Option | Opens above the button | | `left` | {}}>Option | Opens to the left of the button | | `right` | {}}>Option | Opens to the right of the button |
Code example ```tsx {}}>Option {}}>Option {}}>Option {}}>Option ```
## Behavior - Selecting an item triggers the associated action immediately. - The ActionMenu label does not change based on selection. - The menu opens from the trigger and closes after selection, outside click, or cancellation. - The `position` prop controls the preferred direction in which the menu opens: `'bottom'` (default), `'top'`, `'left'`, or `'right'`. The menu automatically adjusts if it would be clipped by the viewport. - Keyboard interaction: - Arrow keys navigate between actions. - Enter activates the focused action. - Escape closes the menu. ## Accessibility - Ensure full keyboard support for all interactions.
(WCAG 2.1.1 — [Keyboard](https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html)) - Menu items have clear visual focus indicators.
([WCAG 2.4.7 — Focus Visible](https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html)) - Each menu item exposes a clear, descriptive action name to assistive technologies.
(WCAG 4.1.2 — [Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html)) - The menu state (open/closed) is communicated programmatically. - Actions are perceivable and understandable without relying on icons alone. ## Implementation Examples ### Share menu Social sharing links. There's a package for [Share Button](https://github.com/Yleisradio/share-button) available for easier sharing functionality. *[Live preview](https://design-system.yle.fi/docs/components/ActionMenu/)*
Code example ```tsx import { ActionMenu, MenuItem } from '@yleisradio/yds-components-react'; import { Link2, Mail, Whatsapp, Facebook, TwitterX, Linkedin } from '@yleisradio/yds-icons-react'; } onSelect={() => navigator.clipboard.writeText('https://example.com')}>Kopioi linkki } onSelect={() => window.open('mailto:?subject=Title&body=https://example.com')}>Sähköposti } onSelect={() => window.open('https://wa.me/?text=https://example.com', '_blank')}>WhatsApp } onSelect={() => window.open('https://www.facebook.com/sharer/sharer.php?u=https://example.com', '_blank')}>Facebook } onSelect={() => window.open('https://x.com/intent/post?url=https://example.com', '_blank')}>X-viestipalvelu } onSelect={() => window.open('https://www.linkedin.com/sharing/share-offsite/?url=https://example.com', '_blank')}>LinkedIn ```
## API Reference ### Props The ActionMenu component accepts all standard Button props (except `as` and `onClick`) in addition to the following props: | Prop | Type | Required | Default | Description | | ----------------- | ------------------- | -------- | --------- | ------------------------------------- | | `text` | `string` | Yes | — | Button label text | | `id` | `string` | Yes | — | Unique identifier for the button | | `menuId` | `string` | Yes | — | Unique identifier for the menu | | `children` | `React.ReactNode` | Yes | — | MenuItem components to display | | `dropdown` | `boolean` | No | `true` | Whether to show dropdown chevron icon | | `openMenuOnClick` | `boolean` | No | `true` | Whether clicking opens the menu | | `onClose` | `() => void` | No | — | Callback fired when menu closes | | `onOpen` | `() => void` | No | — | Callback fired when menu opens | | `menuSize` | `'sm' \| 'md'` | No | `'md'` | Size of menu items | | `alignMenu` | `'left' \| 'right'` | No | `'right'` | Horizontal alignment of menu | | `position` | `'top' \| 'right' \| 'bottom' \| 'left'` | No | `'bottom'` | Preferred direction the menu opens | ### Type Definitions ```tsx export interface ActionMenuDSProps { text: string; id: string; menuId: string; children: ReactNode; dropdown?: boolean; openMenuOnClick?: boolean; onClose?: () => void; onOpen?: () => void; menuSize?: 'md' | 'sm'; alignMenu?: 'left' | 'right'; position?: 'top' | 'right' | 'bottom' | 'left'; } export type ActionMenuProps = ActionMenuDSProps & Omit, 'as' | 'onClick'>; ``` ## Related Components - [DropdownMenu](../DropdownMenu/DropdownMenu.md) – Alternative component for selectable menu options - [Button](../Button/Button.md) – ActionMenu extends Button functionality - [Menu](../Menu/Menu.md) - Base component for ActionMenu # Badge Badges are used to indicate a notification, item count, or other contextual information related to a navigation destination. - [Figma](https://www.figma.com/design/U1NzhzFpMpgaIDL7MbbrYE/YDS-Foundation?node-id=4240-129) - [Storybook](https://ds-storybook.test.yle.fi/?path=/docs/components-badge--docs) - [Edit this page](https://github.com/Yleisradio/yle-design-system/blob/main/docs/components/Badge/Badge.mdx) - [Create a new issue](https://github.com/Yleisradio/yle-design-system/issues/new) *[Live preview](https://design-system.yle.fi/docs/components/Badge/)*
Code example ```tsx import { Badge } from '@yleisradio/yds-components-react'; 3 ```
## Why to Use The Badge component visually indicates a status, count, or notification associated with another UI element. It helps users quickly identify new information, pending actions, or system alerts without needing to open the full view. They are typically displayed inside or attached to another component, such as an icon, button, or tab. Badges are commonly used in navigation or profile contexts — for example, to show the number of unread messages, notifications, or active tasks. They are non-interactive and rely on their placement and appearance to convey meaning clearly. ## When to Use Use Badge when you want to draw attention to updates, counts, or statuses related to another UI element. ## Content guidelines > **Do** > - Attach the badge directly to a relevant component (e.g. icon, button, tab). > - Use it to display numeric counts or simple state indicators. > - Choose a size that keeps the content legible relative to the parent. > **Don’t** > - Don’t use Badge as an interactive control. ## Key Props Use the following props to customize the Badge component to fit your needs. ### size Controls the visual dimensions and typography. Smaller sizes (`sm`, `md`) are suited to minimal indicators (dots); larger sizes (`lg`, `xl`) accommodate numeric content. | Value | Example | Description | |------|---------|------------------| | `sm` | | Tiny status indicator without text. Used in upper right corner of navigation item. | | `md` | | Small status/count indicator without text. Used in upper right corner of navigation item. | | `lg` | 3 | Default size. | | `xl` | 12 | Prominent counts or emphasis. For inline use. |
Code example ```tsx 3 12 ```
### color Defines semantic color scheme. | Value | Example | Description | |-------|---------|---------| | `default` | 4 | Standard action background; generic counts. | | `negative` | 2 | Neutral/subtle emphasis; lower visual weight. | | `highlight` | 12 | High-attention feedback. Use sparingly. |
Code example ```tsx 4 2 12 ```
### variant Alters structural presentation (border & padding). `primary` is a filled pill; `secondary` applies an outline for large sizes (`lg`, `xl`). | Value | Example | Description | |---------|---------|-------------| | `primary` | 12 | Filled default style. | | `secondary` | 12 | Outline improves contrast against background but reduces badge size |
Code example ```tsx 3 3 12 12 ```
## Accessibility - Use `aria-label` when numeric value or color alone lacks context (e.g. `7`). - Keep numbers short; truncate or summarize large counts (e.g. `99+`). - Use ARIA attributes (e.g., aria-label, aria-live) to make changes accessible. - Avoid animations or flashing that may cause distraction or accessibility issues.
Code example ```tsx 7 ```
[See more examples in Storybook](https://ds-storybook.test.yle.fi/?path=/docs/components-badge--docs) ## API Reference ### Props The Badge component accepts all standard HTML `` attributes in addition to the following props: | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `size` | `BadgeSizeProp` | No | `'lg'` | Size of the Badge | | `children` | `React.ReactNode` | No | — | | | `color` | `BadgeColorProp` | No | `'default'` | Color of the Badge | | `variant` | `BadgeVariantProp` | No | `'primary'` | Visual presentation of the Badge | ### Type Definitions ```tsx export type BadgeSizeProp = 'xl' | 'lg' | 'md' | 'sm'; export type BadgeColorProp = 'default' | 'negative' | 'highlight'; export type BadgeVariantProp = 'primary' | 'secondary'; export interface BadgeDSProps { size?: BadgeSizeProp; children?: React.ReactNode; color?: BadgeColorProp; variant?: BadgeVariantProp; } export type BadgeProps = HTMLAttributes & BadgeDSProps; ``` ## Related Components - [Button](../Button/Button.md) – Badges often attach to buttons to show counts. - [NavigationTabs](../NavigationTabs/NavigationTabs.md) – Use Badges to show per-tab notification or item counts. - [Tag](../Tag/Tag.md) – Static keyword label without counts. - [TagLink](../TagLink/TagLink.md) – Interactive keyword navigation. - [TagFilter](../TagFilter/TagFilter.md) – Removable filter chips. # BannerNotification A BannerNotification displays important temporary messages that affect the entire view or service, appearing below the header and remaining visible until dismissed. - [Figma](https://www.figma.com/design/U1NzhzFpMpgaIDL7MbbrYE/YDS-Foundation?m=auto&node-id=7323-2007&t=lI9imRC4H4TmzLE4-1) - [Storybook](https://ds-storybook.test.yle.fi/?path=/docs/components-bannernotification--docs) - [Edit this page](https://github.com/Yleisradio/yle-design-system/blob/main/docs/components/BannerNotification/BannerNotification.mdx) - [Create a new issue](https://github.com/Yleisradio/yle-design-system/issues/new) The service will be unavailable on Sunday from 02:00 to 04:00.
Code example ```tsx import { BannerNotification } from '@yleisradio/yds-components-react'; The service will be unavailable on Sunday from 02:00 to 04:00. ```
## Why to use BannerNotifications communicate important service- or view-wide information that users must notice. They are designed for messages that affect the entire experience, such as maintenance, outages, or access limitations, and therefore require high visibility and persistence. Unlike Inline Notifications, which relate to specific content, or Toasts, which are brief and transient, BannerNotifications provide sustained awareness across the whole view. ## When to use BannerNotifications are used to communicate important information that affects the entire page or service and must be noticed by all users. > **Do** > - Use a BannerNotification for service-wide or view-wide messages. > - Use it for system states such as maintenance, outages, access restrictions, or critical warnings. > - Use it when the message must remain visible until acknowledged or resolved. > - Use it when users may need to take action outside the current context. > **Don’t** > - Don’t use BannerNotifications for local or field-level issues — use Inline [Notification](../Notification/Notification.md). > - Don’t use them for short-lived confirmations or feedback — use a [Toast](../ToastNotification/ToastNotification.md). > - Don’t show multiple banners at the same time. > - Don’t make critical system messages dismissible without another way to access the information. ### Differences between Notification Types | Type | Purpose | Placement | Use case | | ----------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------- | | **Banner Notification** | A notification covering the entire view or service (e.g., maintenance, error state). | Fixed, below the header. | “Maintenance in progress” | | **Inline Notification** | A local notification within content or a form (e.g., field error). | Embedded in content. | “This field is required” | | **Toast Notification** | A short-lived, automatically disappearing feedback message (e.g., save confirmation). | Floating near the bottom of the screen, sliding in and out smoothly. | “Saved successfully” | ## Content Guidelines BannerNotification content should be clear, scannable, and focused on impact. Users should immediately understand what is happening and why it matters. > **Do** > - Use a clear, descriptive title (e.g., “Huoltokatko”). > - Start with what is happening and why it matters (e.g., “Palvelu ei ole käytettävissä sunnuntaina 02.00–04.00”). > - Keep body copy short (one–two sentences). > - Add next steps when relevant (e.g., “Yritä uudelleen”, “Näytä lisätiedot”). > - Use actions only when the user can meaningfully act. > **Don’t** > - Don’t write long or dense text. > - Don’t repeat title text in the body. > - Don’t use vague phrasing (e.g., “Tapahtui virhe”). > - Don’t use jargon or filler. ## Anatomy ![BannerNotification anatomy](../../img/banner-notification-anatomy.svg) 1. **Icon** – Icon indicating the message level (info, success, warning, error). 2. **Title** – Short heading. 3. **Body** – Main message content. 4. **Secondary Button (Actions)** – Optional supporting or alternative action. 5. **Primary Button (Actions)** – Main action guiding the user’s next step. 6. **Close Button** – Optional dismiss control that allows the user to close non-critical or informational notifications. ## Key BannerNotification Props Use these props to configure the BannerNotification component. ### level Sets semantic and visual level (icon + colors). | Value | Example | Description | | --------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------- | | `info` |
General information.
| Neutral informational context. | | `success` | Task completed. | Positive confirmation. | | `warning` | Check details. | Caution, requires attention soon. | | `error` | Action failed. | Critical or blocking issue. |
Code example ```tsx General information. Task completed. Check details. Action failed. ```
### title Optional short heading preceding body. | Type | Example | Description | | ----------- | ------------------------------------------------------------------------------------------------------------- | --------------------------- | | `ReactNode` |
Service updated.
| Concise summary of message. |
Code example ```tsx Service updated. ```
### children (body) Main descriptive text. | Type | Example | Description | | ----------- | -------------------------------------------------------------------------------------------------------------------- | ---------------------------- | | `ReactNode` |
Will occur Sunday.
| Explains status or guidance. |
Code example ```tsx Will occur Sunday. Standalone message without title. ```
### hideIcon Suppresses level icon (text/color still convey status). | Type | Example | Description | | --------- | ------------------------------------------------------------------------------------------------------- | -------------------------------- | | `boolean` |
Plain text only.
| Removes icon for minimal layout. |
Code example ```tsx Plain text only. ```
### onClose (close button) | Type | Example | Description | | ------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | | `() => void` |
{}} title="Dismissable">Close me.
| If provided, displays close button at the right side. Closing the notification needs to be handled in the callback. |
Code example ```tsx const [show, setShow] = useState(true); {show && ( setShow(false)} title="Dismissable"> Close me. )} ```
### actions Inline actionable elements (e.g., Retry, View details). | Type | Example | Description | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | | `ReactNode` |
} title="Beta test">We are looking for user to participate in our beta test.
| Buttons aligned within banner. |
Code example ```tsx
### contentContainer Custom wrapper component to constrain or style inner content. | Type | Example | Description | | ---------------------------------- | --------------------------------------------------------- | ----------------------------------- | | `ComponentType` | [See implementation examples](#constrained-content-width) | Limits width / adds layout styling. | ## Behavior - BannerNotifications appear directly below the global header and span the full width of the viewport. - They remain visible until dismissed by the user or until the underlying condition is resolved. - Only one BannerNotification should be shown per view at a time. - BannerNotifications should not overlap or obscure primary page content. - When actions are present, they are clearly labeled and consistently aligned. - Dismissal is optional and should be avoided for critical system messages. ## Accessibility - Place logically after the header in DOM order for easy navigation. - Ensure close button includes a descriptive aria-label (e.g., “Close notification”). - Ensure full keyboard accessibility and visible focus states for actions and controls. - Critical information should remain available until resolved or acknowledged. ## Implementation examples ### With actions } > Ohjelma katsottavissa EU:n alueella.
Code example ```tsx } > Ohjelma katsottavissa EU:n alueella. ```
### Constrained content width *[Live preview](https://design-system.yle.fi/docs/components/BannerNotification/)* ## API Reference ### Props The BannerNotification component accepts all standard HTML `
` attributes in addition to the following props: | Prop | Type | Required | Default | Description | | ------------------ | ---------------------------------- | -------- | -------- | ----------- | | `children` | `React.ReactNode` | No | — | | | `title` | `React.ReactNode` | No | — | | | `level` | `NotificationLevel` | No | `'info'` | | | `hideIcon` | `boolean` | No | — | | | `onClose` | `() => void` | No | — | | | `actions` | `React.ReactNode` | No | — | | | `contentContainer` | `ComponentType` | No | — | | ### Type Definitions ```tsx export type NotificationLevel = 'info' | 'success' | 'warning' | 'error'; export interface BannerNotificationDSProps { children?: React.ReactNode; title?: React.ReactNode; level?: NotificationLevel; hideIcon?: boolean; onClose?: () => void; actions?: React.ReactNode; contentContainer?: ComponentType; } export type BannerNotificationProps = Omit< HTMLAttributes, keyof BannerNotificationDSProps > & BannerNotificationDSProps; ``` ## Related Components - [Notification](../Notification/Notification.md) – Inline contextual messages. - [Toast Notification](../ToastNotification/ToastNotification.md) – Short-lived floating feedback. # BottomSheet BottomSheet displays content in a panel that slides up from the bottom of the viewport. It is optimized for mobile and touch-based interactions and can be either modal or non-modal. BottomSheet handles gestures, positioning, overlay behavior, and accessibility, and often contains a Dialog component for structured content. - [Figma](https://www.figma.com/design/U1NzhzFpMpgaIDL7MbbrYE/YDS-Foundation?m=auto&node-id=9821-1268&t=lI9imRC4H4TmzLE4-1) - [Storybook](https://ds-storybook.test.yle.fi/?path=/docs/components-bottomsheet--docs) - [Edit this page](https://github.com/Yleisradio/yle-design-system/blob/main/docs/components/BottomSheet/BottomSheet.mdx) - [Create a new issue](https://github.com/Yleisradio/yle-design-system/issues/new) *[Live preview](https://design-system.yle.fi/docs/components/BottomSheet/)*
Code example ```tsx import { BottomSheet, useBottomSheet, Button, Dialog } from '@yleisradio/yds-components-react'; const { bottomSheetRef, openBottomSheet, closeBottomSheet } = useBottomSheet(); <> ```
## Why to use Use BottomSheet to present contextual content, actions, or settings while preserving the user’s sense of place. It provides a mobile-friendly alternative to Modal with natural touch interactions, flexible sizing, and optional non-modal behavior. BottomSheet standardizes mobile dialog behavior across YDS and ensures accessible gesture and keyboard handling. ## When to use Use BottomSheet when content is closely related to the current view and especially in mobile or touch-first contexts. > **Do** > - Use BottomSheet for mobile dialogs, filters, and settings. > - Use it when content should feel lightweight and dismissible. > - Combine BottomSheet with [Dialog](../Dialog/Dialog.md) for structured content. > **Don't** > - Don’t use BottomSheet for critical confirmations that require full attention. > - Don’t use BottomSheet for complex, multi-step workflows. > - Don’t replace desktop modals with BottomSheet unnecessarily. ## Content Guidelines Use these guidelines when placing content inside a BottomSheet. > **Do** > - Keep content task-focused and lightweight. > - Prefer short forms and clear actions. > - Use clear completion actions such as “Done” or “Apply”. > **Don't** > - Don’t overload the sheet with long scrolling content unless using the expanded size. > - Don’t hide critical actions below excessive content. > - Don’t rely on drag-to-close as the only way to dismiss the sheet. ## Anatomy ![BottomSheet anatomy](../../img/bottomsheet-anatomy.svg) 1. **Drag handle / close affordance** – Visual indicator that the sheet can be dragged or dismissed. 2. **Sheet container** – The surface that slides up from the bottom and contains all sheet content. 3. **Bottom Sheet title** – Required heading that describes the purpose of the sheet. 4. **Content area** – The main content section for text or interactive elements. Supports internal scrolling when content exceeds available height. 5. **Actions area (optional)** – Footer area for primary and secondary actions, aligned consistently across devices. ## Key BottomSheet Props Use these props to configure the BottomSheet component. ### bottomSheetRef Ref handle for controlling the bottom sheet (open/close). Use with `useBottomSheet` hook. | Type | Example | Description | | ------------------------------------ | --------------------- | ----------------------------------- | | `React.RefObject` | | Ref handle for programmatic control |
Code example ```tsx const { bottomSheetRef, openBottomSheet, closeBottomSheet } = useBottomSheet();
Content here
```
### title Optional heading text displayed in the sheet header. | Type | Example | Description | | -------- | -------------------------------------- | ------------------------------- | | `string` | | Title text for the bottom sheet |
Code example ```tsx const { bottomSheetRef, openBottomSheet, closeBottomSheet } = useBottomSheet();
Content here
```
### titleAs HTML tag name to render for the title. | Type | Example | Description | | -------- | ---------------------------------- | ----------------------------------- | | `string` | | HTML element for title (e.g., 'h2') |
Code example ```tsx const { bottomSheetRef, openBottomSheet, closeBottomSheet } = useBottomSheet();
Content here
```
### dragToClose Enables pointer drag gesture and handle for closing the sheet. | Value | Example | Description | | ------- | ----------------------------------------- | ------------------------------------ | | `true` | | Enables drag-to-close functionality | | `false` | | Disables drag-to-close functionality |
Code example ```tsx const { bottomSheetRef, openBottomSheet, closeBottomSheet } = useBottomSheet();
Content here
```
### align Horizontal alignment of the sheet. | Value | Example | Description | | -------- | ------------------------------------ | ------------------------------ | | `center` | | Centers the sheet horizontally | | `left` | | Aligns sheet to the left | | `right` | | Aligns sheet to the right |
Code example ```tsx const { bottomSheetRef, openBottomSheet, closeBottomSheet } = useBottomSheet();
Content here
```
### nonModal If true, renders a non-modal dialog (no backdrop, background remains interactive). | Value | Example | Description | | ------- | -------------------------------------- | ------------------------- | | `true` | | Makes the sheet non-modal | | `false` | | Makes the sheet modal |
Code example ```tsx const { bottomSheetRef, openBottomSheet, closeBottomSheet } = useBottomSheet();
Content here
```
### closeOnOutsideClick If true, clicking outside the sheet closes it (modal only). | Value | Example | Description | | ------- | ------------------------------------------------- | --------------------------------------- | | `true` | | Controls outside click closing behavior | | `false` | | Controls outside click closing behavior |
Code example ```tsx const { bottomSheetRef, openBottomSheet, closeBottomSheet } = useBottomSheet();
Content here
```
### size Size variant of the bottom sheet. | Value | Example | Description | | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | | `compact` |
What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like). Where does it come from? Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32. The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham. Where can I get some? There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.
| Compact size variant | | `default` |
What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like). Where does it come from? Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32. The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham. Where can I get some? There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.
| Default size variant | | `expanded` |
What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like). Where does it come from? Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32. The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham. Where can I get some? There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.
| Expanded size variant |
Code example ```tsx Content ```
## Behavior - BottomSheet slides in from the bottom of the viewport and animates smoothly when opening and closing. - The sheet opens when `openBottomSheet()` is called or when the ref’s `open()` method is invoked. - The sheet closes when `closeBottomSheet()` is called, the close button or drag handle is activated, the Escape key is pressed, or the user drags the sheet down past the close threshold. - Drag-to-close interaction is enabled when `dragToClose={true}`. If the drag distance exceeds the threshold (100px), the sheet closes; otherwise, it snaps back into place. - Modal bottom sheets (default) block background interaction and disable body scrolling. - Non-modal bottom sheets (`nonModal={true}`) allow background interaction and do not disable body scrolling. - Focus and keyboard behavior adapt based on modality. - The Escape key always closes modal bottom sheets and closes non-modal sheets only when focus is inside the sheet. - Clicking outside the sheet closes it only when `closeOnOutsideClick={true}` and the sheet is modal. - The `onClose` callback fires after the sheet has fully closed (native dialog `close` event). - Size variants (`compact`, `default`, `expanded`) control how much vertical space the sheet initially occupies. ## Accessibility - BottomSheet uses the native HTML `` element to ensure correct semantics and screen reader support ([WCAG 4.1.2 — Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value)). - Modal BottomSheets trap keyboard focus while open and prevent interaction with background content, whereas non-modal BottomSheets allow background interaction and only handle keyboard events when focus is inside the sheet. - The sheet can always be closed using the Escape key ([WCAG 2.1.1 — Keyboard](https://www.w3.org/WAI/WCAG22/Understanding/keyboard)). - Drag-to-close is a supplementary interaction; keyboard and button-based closing must always be available. ## Implementation Examples ### Game instructions BottomSheet with Dialog *[Live preview](https://design-system.yle.fi/docs/components/BottomSheet/)* ### Nickname BottomSheet with custom content *[Live preview](https://design-system.yle.fi/docs/components/BottomSheet/)*
Code example ```tsx import { BottomSheet, useBottomSheet, Button, TextInput } from '@yleisradio/yds-components-react'; const { bottomSheetRef, openBottomSheet, closeBottomSheet } = useBottomSheet();
Nimimerkin pitää olla asiallinen ja noudattaa hyvää makua.
```
## API Reference ### Props The BottomSheet component accepts all standard HTML `` attributes in addition to the following props: | Prop | Type | Required | Default | Description | | --------------------- | -------------------------------------- | -------- | ----------- | ---------------------------------------------- | | `bottomSheetRef` | `React.RefObject` | No | — | Ref handle for controlling the bottom sheet | | `title` | `string` | No | — | Optional heading text displayed in header | | `titleAs` | `string` | No | — | HTML tag name to render for title (e.g., 'h2') | | `onClose` | `() => void` | No | — | Callback fired after sheet closes | | `dragToClose` | `boolean` | No | `true` | Enables drag-to-close functionality | | `align` | `'center' \| 'left' \| 'right'` | No | `'center'` | Horizontal alignment of the sheet | | `nonModal` | `boolean` | No | `false` | Renders as non-modal dialog | | `closeOnOutsideClick` | `boolean` | No | `true` | Closes sheet on outside click (modal only) | | `size` | `'compact' \| 'default' \| 'expanded'` | No | `'default'` | Size variant of the bottom sheet | | `children` | `React.ReactNode` | No | — | Content displayed inside the sheet | ### Type Definitions ```tsx export interface BottomSheetDSProps { bottomSheetRef?: React.RefObject; title?: string; titleAs?: string; onClose?: () => void; children?: React.ReactNode; dragToClose?: boolean; align?: 'center' | 'left' | 'right'; nonModal?: boolean; closeOnOutsideClick?: boolean; size?: 'compact' | 'default' | 'expanded'; } export type BottomSheetProps = HTMLAttributes & BottomSheetDSProps; export type BottomSheetHandle = { open: () => void; close: () => void; }; ``` ### useBottomSheet Hook The `useBottomSheet` hook provides a convenient way to manage bottom sheet state. #### Returns | Property | Type | Description | | ------------------ | ------------------------------------ | ----------------------------------------------- | | `bottomSheetRef` | `React.RefObject` | Ref object to pass to the BottomSheet component | | `openBottomSheet` | `() => void` | Function to open the bottom sheet | | `closeBottomSheet` | `() => void` | Function to close the bottom sheet | ## Related Components - [Modal](../Modal/Modal.md) – Alternative modal component. - [Dialog](../Dialog/Dialog.md) – Often used inside BottomSheet for content structure. - [Accordion](../Accordion/Accordion.md) – Can be used inside a BottomSheet to organise collapsible content. # Button A Button triggers an action or confirms a choice when the user activates it. - [Figma](https://www.figma.com/design/U1NzhzFpMpgaIDL7MbbrYE/YDS-Foundation?node-id=2151-9310) - [Storybook](https://ds-storybook.test.yle.fi/?path=/docs/components-button--docs) - [Edit this page](https://github.com/Yleisradio/yle-design-system/blob/main/docs/components/Button/Button.mdx) - [Create a new issue](https://github.com/Yleisradio/yle-design-system/issues/new) *[Live preview](https://design-system.yle.fi/docs/components/Button/)*
Code example ```tsx import { Button } from '@yleisradio/yds-components-react';
## Why to use Buttons are used to execute actions in the interface: submitting a form, opening a modal, cancelling an operation, or performing add/remove actions. They stand out visually from surrounding content and guide the user’s attention to the most important actions. The button text must clearly communicate what happens when it is pressed (for example, “Poista kaikki” or “Lähetä lomake”). It should typically start with a verb and use sentence case. ## When to use Use a Button when the user needs to perform an explicit action. Buttons initiate tasks, submit information, confirm decisions, or open additional functionality. Use a Button whenever the user is expected to do something, not just navigate or view content. > **Do** > - Use a Button when an action changes data, advances a task, or opens an interaction. > - Use the Primary button for the main action in a section or flow. > - Use Secondary and Text buttons for supporting or lower-priority actions. > - Use a Button instead of a link when something should happen on the same page. > - Use icon-only buttons only when they fit the interaction pattern, the icon is widely recognized, or space is limited. > **Don’t** > - Don’t use a Button for simple navigation — use a [Link](../Link/Link.md) instead. > - Don’t use a Button when the action is ambiguous or when a menu would better support the flow. > - Don’t use multiple Primary buttons in the same section — it weakens the main action. > - Don’t use disabled buttons to explain why something can’t be done — use helper or error text instead. > - Don’t use very small button sizes as the default, especially on touch devices. ## Content Guidelines Button labels must be short, clear, and action-oriented. They tell the user exactly what will happen when the button is pressed. Good labels increase confidence and reduce cognitive load. - Start labels with a clear verb (e.g., Tallenna, Jatka, Muokkaa, Poista). - Keep labels brief — ideally 1–2 words. - Use consistent wording across the product (e.g., always use Peruuta for dismissing actions). - Don’t use vague or generic labels like OK, Hyväksy, or Klikkaa tästä. - Don’t use humor, jargon, or internal terminology unfamiliar to users. - Don’t rely on icon-only labels without accessible text. ### Usage in UI patterns #### Text labels Use concise, action-oriented labels starting with a verb without unnecessary words or politeness (e.g., Tallenna, Jatka, Muokkaa, Poista). **Do:** Good: Clear and concise. **Don't:** Bad: Unnecessary politeness. **Do:** Good: Clear and concise. **Don't:** Bad: Unnecessary words. #### Short labels Keep labels brief — ideally 1–2 words. Avoid long phrases or sentences. **Do:** Good: Short and to the point. **Don't:** Bad: Too long and wordy. #### Icons Even with an icon, the label must clearly describe the action (e.g., “Poista tiedosto” with a trash can icon), not just an icon. **Do:** Good: Text describes the action. **Don't:** Bad: Icon alone is unclear. #### Forms The label should describe the result of submitting (e.g., “Lähetä lomake”, “Tallenna muutokset”), not the action “Lähetä”. **Do:** Good: Describes the result of the action. **Don't:** Bad: Vague about what happens. > #### Dialogs & confirmations The primary button must clearly confirm the intended action (e.g., “Poista tiedosto”, “Hyväksy ehdot”), while secondary actions use neutral wording (e.g., “Peruuta”). **Do:** Good: Clearly states the action. **Don't:** Bad: Ambiguous confirmation. #### Destructive actions Make the consequence explicit to prevent mistakes (e.g., “Poista kaikki”, “Tyhjennä lista”). Avoid ambiguous verbs like “Poista” ilman kohdetta. **Do:** Good: Specifies what will be deleted. **Don't:** Bad: Unclear about the target. #### Navigation Labels should indicate where the user will go (e.g., “Jatka eteenpäin”, “Avaa asetukset”, “Näytä lisää”). **Do:** Good: Indicates the destination. **Don't:** Bad: Vague about where it leads. ## Anatomy ![Button anatomy](../../img/button-anatomy.svg) 1. **Container** – Clickable target 2. **Label** – Action text 3. **Icon Before (Optional)** – Visual cue preceding text 4. **Icon After (Optional)** – Supplementary indicator ## Key Button Props Use these props to configure the Button component. ### text Visible label text of the button. Explains the action performed when pressed. | Type | Example | Description | |------|---------|-------------| | `string` |
| Makes the button stretch horizontally. |
Code example ```tsx
```
### iconBefore Adds icon component before the text. | Type | Example | Description | |------|---------|-------------| | `ReactNode` | }>Content | Footer action elements |
Code example ```tsx } >

Are you sure?

```
## Behavior - Dialog provides layout only — no open/close logic. - Body content scrolls independently when height exceeds limits. - Scroll separators appear automatically when content overflows. - Actions remain visually separated from content. - Action buttons in the `actions` prop are automatically sized to match the dialog's `size` prop - Illustrative icons are positioned above the title and affect spacing accordingly - The component is purely presentational — it does not handle opening, closing, or modal behavior (that's handled by Modal or BottomSheet) ## Accessibility - Title is rendered as a heading (default `h2`, configurable via `titleAs` prop) for proper document structure
([WCAG 1.3.1 — Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships)) - Structure supports screen reader announcement when used in Modal/BottomSheet. - Illustrative icons are decorative and don't require alt text (they're visual enhancements) ## API Reference ### Props The Dialog component accepts all standard HTML `
` attributes in addition to the following props: | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `title` | `string` | Yes | — | Dialog title text | | `titleAs` | `string` | No | `'h2'` | HTML tag name to render for title | | `illustrativeIcon` | `React.ReactElement \| null` | No | — | Optional icon displayed above title | | `size` | `'sm' \| 'md'` | No | `'md'` | Size variant of the dialog | | `children` | `React.ReactNode` | No | — | Content displayed in dialog body | | `actions` | `React.ReactNode` | No | — | Action buttons displayed in footer | ### Type Definitions ```tsx import { type SvgProps } from '@yleisradio/yds-icons-react'; export interface DialogDSProps { title: string; titleAs?: string; illustrativeIcon?: React.ReactElement | null; size?: 'sm' | 'md'; children?: React.ReactNode; actions?: React.ReactNode; } export type DialogProps = HTMLAttributes & DialogDSProps; ``` ## Related Components - [Modal](../Modal/Modal.md) – **Primary container for Dialog** — use Modal to wrap Dialog for desktop/tablet dialog interfaces. - [BottomSheet](../BottomSheet/BottomSheet.md) – **Primary container for Dialog** — use BottomSheet to wrap Dialog for mobile dialog interfaces. - [Button](../Button/Button.md) – Commonly used in dialog `actions` prop for footer buttons. - [Accordion](../Accordion/Accordion.md) – Can be used inside a Dialog to organise collapsible content. # DropdownMenu DropdownMenu is a button-based control that lets users select a value from a list of options and applies the change immediately. It is used for lightweight state changes such as language selection or sorting. - [Figma](https://www.figma.com/design/U1NzhzFpMpgaIDL7MbbrYE/YDS-Foundation?node-id=8809-10633) - [Storybook](https://ds-storybook.test.yle.fi/?path=/docs/components-dropdownmenu--docs) - [Edit this page](https://github.com/Yleisradio/yle-design-system/blob/main/docs/components/DropdownMenu/DropdownMenu.mdx) - [Create a new issue](https://github.com/Yleisradio/yle-design-system/issues/new) *[Live preview](https://design-system.yle.fi/docs/components/DropdownMenu/)*
Code example ```tsx import { DropdownMenu } from '@yleisradio/yds-components-react'; const [value, setValue] = useState(); ```
## Why to use DropdownMenu provides a compact and accessible way to change the UI state by selecting one option from a predefined list. It enables users to make immediate choices without additional confirmation, while keeping the interface clean and uncluttered. By building on the Menu component, DropdownMenu ensures consistent positioning, keyboard interaction, and visual behavior across products. It is especially suitable for cases where options are few, clearly comparable, and the selection directly affects what the user sees or how the interface behaves. ## When to use Use DropdownMenu when users need to select a single value that changes the UI state immediately, such as language, sorting order, or filtering mode. > **Do** > - Use when the selection updates the interface immediately without requiring a separate save action. > - Use when options are easily comparable at a glance. > - Use for lightweight UI state changes such as language, sorting, or view mode. > **Don't** > - Don’t use when users need to select multiple values – use multi-select patterns instead. > - Don’t use when options should remain visible and directly comparable – use [ButtonGroup](../ButtonGroup/ButtonGroup.md) instead. > - Don’t use for triggering actions that do not change component state – use [ActionMenu](../ActionMenu/ActionMenu.md) instead. ## Content Guidelines DropdownMenu labels and options should support quick scanning and easy comparison. > **Do** > - Keep option labels short and parallel in structure. > - Use nouns or adjectives for option labels (e.g. “Suomi”, “English”, “Uusin”). > - Ensure options are easily comparable at a glance. > - Prefer fewer than eight options to maintain clarity. > **Don't** > - Don’t use long or descriptive sentences as options. > - Don’t use abbreviations like “FI”, “EN” for language choices. > - Don’t mix different grammatical styles in the same list. > - Don’t include options that are not logically comparable. ## Anatomy ![DropdownMenu anatomy](../../img/dropdown-anatomy.svg) 1. **DropdownMenu button** – The trigger button that opens the dropdown menu. 2. **Label / selected value** – Text inside the button showing the placeholder or the currently selected value. 3. **Chevron icon** – Indicates that the button opens a dropdown menu. 4. **Menu surface** – The floating container holding the selectable options. 5. **MenuItem (option)** – A single selectable option inside the menu. 6. **Selected state** – Visual indicator showing the currently selected option. 7. **MenuSeparator (optional)** – Groups related actions. ## Key DropdownMenu Props Use these props to configure the DropdownMenu component. ### placeholder Placeholder text displayed when no option is selected. | Type | Example | Description | |------|---------|-------------| | `string` | | Placeholder text |
Code example ```tsx ```
### options Array of menu options and separators. | Type | Example | Description | |------|---------|-------------| | `(DropdownMenuOption \| DropdownMenuSeparator)[]` | | Array of options and separators |
Code example ```tsx ```
### dropdown Whether to show a dropdown chevron icon. | Value | Example | Description | |------|---------|-------------| | `true` | | Shows chevron icon | | `false` | | Hides chevron icon |
Code example ```tsx ```
### openMenuOnClick Whether clicking the button opens the menu. | Type | Example | Description | |------|---------|-------------| | `boolean` | | Controls if click opens menu |
Code example ```tsx ```
### menuSize Size of the menu items. | Value | Example | Description | |-------|---------|-------------| | `sm` | | Small menu items | | `md` | | Medium menu items (default) |
Code example ```tsx ```
### alignMenu Horizontal alignment of the menu relative to the button. | Value | Example | Description | |-------|---------|-------------| | `left` | | Aligns menu to the left | | `right` | | Aligns menu to the right (default) |
Code example ```tsx ```
### position Preferred direction in which the menu opens relative to the trigger button. The menu automatically adjusts if it would be clipped by the viewport. | Value | Example | Description | |----------|---------|-------------| | `bottom` | | Opens below the button (default) | | `top` | | Opens above the button | | `left` | | Opens to the left of the button | | `right` | | Opens to the right of the button |
Code example ```tsx ```
## Behavior - Selecting an option updates the component state immediately. - The selected value is reflected in the trigger button label. - The menu opens from the trigger and closes after selection, outside click, or cancellation. - The `position` prop controls the preferred direction in which the menu opens: `'bottom'` (default), `'top'`, `'left'`, or `'right'`. The menu automatically adjusts if it would be clipped by the viewport. - Keyboard interaction: - Arrow keys navigate between options. - Enter selects an option. - Escape closes the menu. ## Accessibility - Ensure full keyboard support for all interactions (Tab, Arrow keys, Enter, Space).
(WCAG 2.1.1 — [Keyboard](https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html)) - Interactive elements have visible focus indicators.
([WCAG 2.4.7 — Focus Visible](https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html)) - Options are easily comparable and perceivable at a glance. - The selected option is communicated both visually and programmatically. - Screen readers announce the current value and the open/closed state of the menu. ## Implementation Examples ### Language Selector *[Live preview](https://design-system.yle.fi/docs/components/DropdownMenu/)*
Code example ```tsx import { DropdownMenu } from '@yleisradio/yds-components-react'; const [value, setValue] = useState(); ```
## API Reference ### Props The DropdownMenu component accepts all standard ActionMenu props (except `children` and `text`) in addition to the following props: | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `placeholder` | `string` | Yes | — | Placeholder text when no option selected | | `value` | `T \| undefined` | Yes | — | Currently selected value | | `onChange` | `(value: T) => void` | Yes | — | Callback fired when selection changes | | `options` | `(DropdownMenuOption \| DropdownMenuSeparator)[]` | Yes | — | Array of menu options and separators | | `id` | `string` | Yes | — | Unique identifier for the button | | `menuId` | `string` | Yes | — | Unique identifier for the menu | ### Type Definitions ```tsx export interface DropdownMenuOption { text: string; value: T; type?: 'item'; } export interface DropdownMenuSeparator { type: 'separator'; } export type DropdownMenuDSProps = Omit & { placeholder: string; value: T | undefined; onChange: (value: T) => void; options: (DropdownMenuOption | DropdownMenuSeparator)[]; }; export type DropdownMenuProps = DropdownMenuDSProps & Omit; ``` ## Related Components - [Menu](../Menu/Menu.md) - Base component for all menu components - [ActionMenu](../ActionMenu/ActionMenu.md) – Base component that DropdownMenu extends - [DropdownMenuGroup](../DropdownMenuGroup/DropdownMenuGroup.md) – Component for grouping multiple DropdownMenus - [Select](../Select/Select.md) – Alternative component for form selects - [ButtonGroup](../ButtonGroup/ButtonGroup.md) – Alternative component for button-based menus # 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. - [Figma](https://www.figma.com/design/U1NzhzFpMpgaIDL7MbbrYE/YDS-Foundation?node-id=6298-3045) - [Storybook](https://ds-storybook.test.yle.fi/?path=/docs/components-dropdownmenugroup--docs) - [Edit this page](https://github.com/Yleisradio/yle-design-system/blob/main/docs/components/DropdownMenuGroup/DropdownMenuGroup.mdx) - [Create a new issue](https://github.com/Yleisradio/yle-design-system/issues/new) *[Live preview](https://design-system.yle.fi/docs/components/DropdownMenuGroup/)*
Code example ```tsx import { DropdownMenuGroup } from '@yleisradio/yds-components-react'; setSelectedValue(value)} /> setSelectedValues(values)} /> ```
## 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. | Pattern | What it does | Typical use cases | |-------|---------|---------| | **Single Select** | Allows selecting one option from a dropdown menu. | Selecting a single election-related view or setting, such as election type or region. | | **Multi Select** | Allows 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](../ButtonGroup/ButtonGroup.md) or tabs instead. > - Don’t use DropdownMenuGroup for page-level navigation — use [NavigationTabs](../NavigationTabs/NavigationTabs.md) instead. > - Don’t use it for switching content within a section — use [SectionTabs](../SectionTabs/SectionTabs.md) instead. > - Don’t combine unrelated selections within the same group. > - Don’t use menus when options should remain visible and directly comparable — use [ButtonGroup](../ButtonGroup/ButtonGroup.md) 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](../../img/dropdown-menu-group-anatomy.svg) 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. | Type | Example | Description | |------|---------|-------------| | `React.ReactNode` | {}}>Menu 1 {}}>Menu 2 | DropdownMenu sub-components |
Code example ```tsx {}}>Menu 1 {}}>Menu 2 ```
### variant Visual style variant of the buttons. | Value | Example | Description | |-------|---------|-------------| | `primary` | {}}>Menu 1 | Primary button style (default) | | `text` | {}}>Menu 1 | Low-emphasis text style for lighter controls |
Code example ```tsx {}}>Menu 1 ```
### size Size of the buttons. | Value | Example | Description | |-------|---------|-------------| | `xs` | {}}>Menu 1 | Extra small size | | `sm` | {}}>Menu 1 | Small size | | `md` | {}}>Menu 1 | Medium size (default) | | `lg` | {}}>Menu 1 | Large size |
Code example ```tsx {}}>Menu 1 ```
### menuSize Size of the menu items. | Value | Example | Description | |-------|---------|-------------| | `sm` | {}}>Menu 1 | Small menu items | | `md` | {}}>Menu 1 | Medium menu items (default) |
Code example ```tsx {}}>Menu 1 ```
## Key DropdownMenuGroup.DropdownMenu Props Use these props to configure the single-select dropdown menu. ### options Array of menu options and separators. | Type | Example | Description | |------|---------|-------------| | `(DropdownMenuOption \| DropdownMenuSeparator)[]` | {}} id="menu-1">Menu 1 | Array of options and separators |
Code example ```tsx Menu 1 ```
### value Currently selected value (single value). | Type | Example | Description | |------|---------|-------------| | `T \| undefined` | {}} id="menu-1" >Menu 1 | Selected option value |
Code example ```tsx Menu 1 ```
### defaultValue Initial selected value for uncontrolled component. | Type | Example | Description | |------|---------|-------------| | `T \| undefined` | {}} id="menu-1">Menu 1 | Initial selected value |
Code example ```tsx Menu 1 ```
### menuItemVariant Visual variant of menu items. | Value | Example | Description | |-------|---------|-------------| | `default` | {}}>Menu 1 | Default menu item style | | `radio` | {}}>Menu 1 | Radio button style menu items |
Code example ```tsx Menu 1 ```
### alignMenu Horizontal alignment of the menu relative to the button. | Value | Example | Description | |-------|---------|-------------| | `left` | {}}>Menu 1 | Aligns menu to the left | | `right` | {}}>Menu 1 | Aligns menu to the right (default) |
Code example ```tsx Menu 1 ```
## Key DropdownMenuGroup.DropdownMenuMultiSelect Props Use these props to configure the multi-select dropdown menu. ### options Array of menu options and separators. | Type | Example | Description | |------|---------|-------------| | `(DropdownMenuOption \| DropdownMenuSeparator)[]` | {}} id="menu-1">Menu 1 | Array of options and separators |
Code example ```tsx Menu 1 ```
### value Currently selected values (array). | Type | Example | Description | |------|---------|-------------| | `T[] \| undefined` | {}} id="menu-1">Menu 1 | Selected option values |
Code example ```tsx Menu 1 ```
### defaultValue Initial selected values for uncontrolled component. | Type | Example | Description | |------|---------|-------------| | `T[] \| undefined` | {}} id="menu-1">Menu 1 | Initial selected values |
Code example ```tsx Menu 1 ```
### menuItemVariant Visual variant of menu items. | Value | Example | Description | |-------|---------|-------------| | `default` | {}}>Menu 1 | Default menu item style | | `checkbox` | {}}>Menu 1 | Checkbox style menu items |
Code example ```tsx Menu 1 ```
### alignMenu Horizontal alignment of the menu relative to the button. | Value | Example | Description | |-------|---------|-------------| | `left` | {}}>Menu 1 | Aligns menu to the left | | `right` | {}}>Menu 1 | Aligns menu to the right (default) |
Code example ```tsx Menu 1 ```
## 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](https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html)) - Provide clear context for each menu through visible text or an accessible programmatic name.
(WCAG 4.1.2 — [Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html)) - Keep selection state perceivable (radio/checkbox variants help communicate single vs multi selection).
(WCAG 1.3.1 — [Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html)) ## Implementation Examples ### Search filters with single select menus } iconClear={createClearIcon({ ariaLabel: 'Tyhjennä haku' })} /> {}} > Valitse palvelu {}} > Valitse aikaväli {}} > Valitse kieli
Code example ```tsx
} iconClear={createClearIcon({ ariaLabel: 'Tyhjennä haku' })} /> {}} > Valitse palvelu {}} > Valitse aikaväli {}} > Valitse kieli
```
### Vaalikone filters with multi-select menus *[Live preview](https://design-system.yle.fi/docs/components/DropdownMenuGroup/)* ## API Reference ### DropdownMenuGroup Props The DropdownMenuGroup component accepts all standard HTML `
` attributes in addition to the following props: | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `children` | `React.ReactNode` | Yes | — | DropdownMenu 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 | ### DropdownMenuGroup.DropdownMenu Props | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `id` | `string` | Yes | — | Unique identifier for the button | | `options` | `(DropdownMenuOption \| DropdownMenuSeparator)[]` | Yes | — | Array of menu options | | `value` | `T \| undefined` | No | — | Selected value (controlled) | | `defaultValue` | `T \| undefined` | No | — | Initial selected value (uncontrolled) | | `onChange` | `(value: T, id: string) => void` | No | — | Callback when selection changes | | `menuItemVariant` | `'default' \| 'radio'` | No | `'default'` | Visual variant of menu items | | `alignMenu` | `'left' \| 'right'` | No | `'right'` | Horizontal alignment of menu | | `onOpen` | `() => void` | No | — | Callback when menu opens | | `onClose` | `() => void` | No | — | Callback when menu closes | | `openMenuOnClick` | `boolean` | No | `true` | Whether clicking opens menu | ### DropdownMenuGroup.DropdownMenuMultiSelect Props | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `id` | `string` | Yes | — | Unique identifier for the button | | `options` | `(DropdownMenuOption \| DropdownMenuSeparator)[]` | Yes | — | Array of menu options | | `value` | `T[] \| undefined` | No | — | Selected values (controlled) | | `defaultValue` | `T[] \| undefined` | No | — | Initial selected values (uncontrolled) | | `onChange` | `(value: T[], id: string) => void` | No | — | Callback when selection changes | | `menuItemVariant` | `'default' \| 'checkbox'` | No | `'default'` | Visual variant of menu items | | `alignMenu` | `'left' \| 'right'` | No | `'right'` | Horizontal alignment of menu | | `onOpen` | `() => void` | No | — | Callback when menu opens | | `onClose` | `() => void` | No | — | Callback when menu closes | | `openMenuOnClick` | `boolean` | No | `true` | Whether clicking opens menu | ### Type Definitions ```tsx export interface DropdownMenuGroupDSProps { children: React.ReactNode; variant?: 'primary' | 'text'; size?: 'xs' | 'sm' | 'md' | 'lg'; menuSize?: 'sm' | 'md'; } export type DropdownMenuGroupProps = Omit, 'onChange'> & DropdownMenuGroupDSProps; export type DropdownMenuGroupDropdownMenuSingleSelectProps = { id: string; options?: (DropdownMenuOption | 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 = { id: string; options?: (DropdownMenuOption | DropdownMenuSeparator)[]; value?: T[]; defaultValue?: T[]; onChange?: (value: T[], id: string) => void; menuItemVariant?: 'default' | 'checkbox'; alignMenu?: 'left' | 'right'; onOpen?: () => void; onClose?: () => void; openMenuOnClick?: boolean; }; ``` ## Related Components - [DropdownMenu](../DropdownMenu/DropdownMenu.md) – Standalone dropdown menu component - [ActionMenu](../ActionMenu/ActionMenu.md) – Base component used internally - [MenuItem](../Menu/Menu.md) – Used internally for menu items - [ButtonGroup](../ButtonGroup/ButtonGroup.md) – Alternative for single-selection groups with form semantics - [CheckboxGroup](../Checkbox/Checkbox.md) – Alternative for multiple selection in forms - [NavigationTabs](../NavigationTabs/NavigationTabs.md) – Page-level navigation between major sections - [SectionTabs](../SectionTabs/SectionTabs.md) – Switching content within a single page or section # FormElementDescription A FormElementDescription is a text component used to provide additional help text, guidance, or clarifications below form labels and inputs. It helps users understand what information is expected or required for a specific field, improving form usability and reducing errors. *[Live preview](https://design-system.yle.fi/docs/components/FormElementDescription/)*
Code example ```tsx import { FormElementDescription } from '@yleisradio/yds-components-react'; ```
## Why to use FormElementDescription enhances form clarity and user guidance. Using this component provides: - **User guidance**: Provides contextual help and clarification about what input is expected - **Error prevention**: Clear instructions reduce user mistakes and form abandonment - **Accessibility**: Works with form labels and inputs to create a complete accessible form experience ## When to use Use the **FormElementDescription** component only when you're building a custom form (e.g. native inputs with [FormElementLabel](../FormElementLabel/FormElementLabel.md)) and need help text below a field. For YDS form components (TextInput, Select, TextArea, etc.), use their `label` and `description` props instead of this component. > **Do** > - Format requirements (e.g. date, phone) or input constraints (length, characters) > - A short example of valid input or optional-field hint > **Don't** > - Don't use with YDS form components — use the form component's `label` and `description` props instead > - Don't use as the field label — use [FormElementLabel](../FormElementLabel/FormElementLabel.md) or the form component's `label` prop > - Don't use for validation errors — use the form element's error state or error prop > - Don't put critical or required info only in the description — keep it in the label or visible UI ## Writing Guidelines > **Do** > - Write in a conversational, helpful tone > - Use concrete examples when describing formats or requirements > - Keep descriptions short and concise > **Don't** > - Don't use overly technical language or jargon > - Don't write in all caps or use excessive punctuation > - Don't make descriptions longer than one or two sentences > - Don't use descriptions to explain why a field is required (use the isRequired prop on FormElementLabel instead) ## Key FormElementDescription Props Use these props to configure the FormElementDescription component. ### text The descriptive text content displayed below the form input. This provides helpful guidance or clarification. | Type | Example | Description | | -------- | ---------------------------------------------------- | -------------------------------------- | | `string` | | Help text displayed in the description |
Code example ```tsx ```
## Behavior - The description text is displayed as a paragraph element below the form input - The component is always visible (no hidden states) - Text styling is consistent across all form descriptions for visual hierarchy - The description is associated with its form control through proximity and context ## Accessibility - The description should be associated with its form input using the `id` attribute and `aria-describedby` - Help text is available to screen reader users and provides important context - The component uses semantic HTML (`

` element) for proper document structure ## API Reference ### Props The FormElementDescription component accepts all standard HTML `

` attributes in addition to the following props: | Prop | Type | Required | Default | Description | | ------ | -------- | -------- | ------- | ------------------------------------ | | `text` | `string` | No | `''` | The descriptive help text to display | ### Type Definitions ```tsx export interface FormElementDescriptionProps extends React.LabelHTMLAttributes { text?: string; } ``` ## Related Components - [FormElementLabel](../FormElementLabel/FormElementLabel.md) – Labels for form inputs - [FormGroupLabel](../FormGroupLabel/FormGroupLabel.md) – Labels for groups of related form elements # FormElementLabel A FormElementLabel is a text component used to label form elements like inputs, textareas, and select fields. It provides accessible labeling and visual hierarchy for form fields, ensuring clear associations between labels and their corresponding form controls. *[Live preview](https://design-system.yle.fi/docs/components/FormElementLabel/)*

Code example ```tsx import { FormElementLabel } from '@yleisradio/yds-components-react'; ```
## Why to use FormElementLabel ensures proper semantic HTML and accessibility standards for form labels. Using this component provides: - **Accessibility**: Properly associates labels with form inputs through the `for` attribute, improving usability for screen reader users - **Consistency**: Provides a unified styling and behavior across all form labels in Yle applications - **Flexibility**: Supports visual indicators for required fields, optional fields, disabled states, and visually-hidden labels for specific use cases - **Built-in spacing**: Manages margin and spacing automatically, reducing the need for custom CSS ## When to use Use FormElementLabel when you need a visible, accessible label for a single form control. > **Do** > - Custom form elements that need to align with YDS form components > **Don't** > - Don't use for content that isn't `label` element — for headings that group multiple fields (eg. Radios and Checkboxes), use [FormGroupLabel](../FormGroupLabel/FormGroupLabel.md) instead > - Don't use FormElementLabel for YDS form components – use the form component's `label` and `labelOptions` props instead ## Writing Guidelines > **Do** > - Write labels in active voice and from the user's perspective (e.g., "Your email address" instead of "Email address to be entered") > - Use simple, familiar words that users understand without needing to look up definitions > - Keep labels concise but descriptive enough to be useful > - Use sentence case for consistency (capitalize the first word only) > - Include units of measurement in labels when relevant (e.g., "Weight (kg)") > - Be specific about what information is expected (e.g., "Phone number (with country code)") > **Don't** > - Don't use all caps or excessive punctuation in labels > - Don't repeat words from the placeholder text in your label > - Don't use abbreviations unless they're universally understood > - Don't make labels overly long - break into multiple labels if needed > - Don't use ambiguous or vague wording like "Enter information" or "Add details" ## Key FormElementLabel Props Use these props to configure the FormElementLabel component. ### label The main text content displayed for the label. This is the primary text that describes the associated form element. | Type | Example | Description | | -------- | -------------------------------------- | --------------------------- | | `string` | | Text displayed in the label |
Code example ```tsx ```
### isRequired Indicates whether the associated form field is required. When true, the label is typically styled to show it's mandatory. | Type | Example | Description | | --------- | --------------------------------------------- | --------------------------- | | `boolean` | | Marks the field as required |
Code example ```tsx ```
### optionalLabel Additional text displayed alongside the main label to indicate optional fields. Only shows when `isRequired` is false. | Type | Example | Description | | -------- | ------------------------------------------------------------- | ------------------------------ | | `string` | | Text indicating optional field |
Code example ```tsx ```
### isDisabled Disables the label visually, typically used when the associated form field is disabled. | Type | Example | Description | | --------- | ------------------------------------------------------ | -------------------------------- | | `boolean` | | Displays label in disabled state |
Code example ```tsx ```
### isHidden Hides the label visually while keeping it accessible to screen readers (using the `visually-hidden` class). | Type | Example | Description | | --------- | -------------------------------------------- | ------------------------------- | | `boolean` | | Hides label from visual display |
Code example ```tsx ```
### hasMarginBottom Controls whether the label includes bottom margin spacing. Enabled by default to provide spacing from the associated form element. | Type | Example | Description | | --------- | ------------------------------------------------- | ------------------------------- | | `boolean` | | Adds bottom margin to the label |
Code example ```tsx ```
## Behavior - The label is always associated with the input and appears above or beside it. - The label text displays with standard styling by default. - When `isRequired` is false, `optionalLabel` is displayed alongside the main label. - When `optionalLabel` is provided, it displays alongside the main label for optional fields (when `isRequired` is false). - Disabled labels appear with reduced styling when the associated input is disabled. - Visually-hidden labels remain accessible to screen readers while not displaying on the page. - Bottom margin is applied by default to create spacing from the associated input. - The label can be associated with form controls using the `htmlFor` attribute or by wrapping the control. ## Accessibility - The label uses semantic `