Get started with yds-components-react 3.0
Install the packages, put design tokens on :root, and render a component. styled-components is not required. This page is for applications that consume @yleisradio/yds-components-react. Component APIs, tokens, and usage examples elsewhere on this site do not require installing the package.
Coming from 2.x? Breaking changes in React package 3.0 is the upgrade guide. If the app keeps styled-components as its own styling library, also see Using styled-components with YDS 3.0.
1. Install
npm install @yleisradio/yds-core @yleisradio/yds-icons-react @yleisradio/yds-components-react
2. Load Yle Next
Components expect the Yle Next variable font. Simplest way to get it is to add the design-system CDN stylesheet in <head>:
<link rel="stylesheet" href="https://design-system.cdn.yle.fi/fonts/fonts.css" />
That file is the supported entry point for every font family the design system ships. It can grow as more styles are added.
To write your own @font-face rules, the variable files are:
To host the fonts locally in the app instead of loading it from the CDN, install @yleisradio/yds-fonts and import the necessary files from the package:
npm install @yleisradio/yds-fonts
3. Put tokens on :root
Components read var(--yds-color-*), spacing, radius, typography and shadow variables. Those must exist on a DOM ancestor — for the whole app, import them once as global CSS.
/* yds-tokens.css */
@import '@yleisradio/yds-core/tokens/build/css/_border-radius.css';
@import '@yleisradio/yds-core/tokens/build/css/_spacing.css';
@import '@yleisradio/yds-core/tokens/build/css/_typography.css';
@import '@yleisradio/yds-core/tokens/build/css/_shadow-default.css';
@import '@yleisradio/yds-core/tokens/build/css/_shadow-default-prefers-dark.css';
@import '@yleisradio/yds-core/tokens/build/css/_theme-default.css';
@import '@yleisradio/yds-core/tokens/build/css/_theme-default-prefers-dark.css';
import './yds-tokens.css';
Import this file at the top of the app entry, before any component that pulls in YDS CSS. Missing a token file shows up as unstyled components, not a JavaScript error.
Alternatively, YdsThemeProvider can wrap a subtree (or, as a migration step, :root) instead of importing these files.
| App theme | Token files |
|---|---|
| Light, following the OS | The list above |
| Always light | Omit the two *-prefers-dark files |
| Always dark | _shadow-default-dark.css and _theme-default-dark.css instead of the light default + prefers-dark pair |
| Switch between light and dark | _theme-light.css / _theme-dark.css and _shadow-light.css / _shadow-dark.css — use .yds-theme-light and .yds-theme-dark on the application root |
Wrappers: development vs production
If a parent page already loads the same files, do not import them again.
Yle apps often sit inside a wrapper. Who owns :root then depends on the environment:
| Environment | Who sets tokens |
|---|---|
| Production | The wrapper usually already delivers the page theme. Skip the :root imports so the same variables are not defined twice. |
| Development | There is often no wrapper. Import the token CSS yourself (the list above) so components are not unstyled. |
If the app needs its own color or shadow tokens while the wrapper still owns the rest of the page, do not write them to :root — that would override the host. Inject a *CssFragment from @yleisradio/yds-core and scope it to the application ID the wrapper puts on your root:
import { darkThemeCssFragment, darkShadowCssFragment } from '@yleisradio/yds-core';
const applicationId = 'your-application-id';
const style = document.createElement('style');
style.textContent = `#${applicationId} {
${darkThemeCssFragment}
${darkShadowCssFragment}
}`;
document.head.appendChild(style);
Preset fragments: lightThemeCssFragment, darkThemeCssFragment, lightShadowCssFragment, darkShadowCssFragment. For a generated Theme, use themeToCssFragment. themeToCss from @yleisradio/yds-components-react wraps the same fragments in a selector if you prefer not to concatenate them by hand.
What about YdsThemeProvider?
You no longer need YdsThemeProvider around the app if the theme is served by the methods above. It can still be used as an alternative way to set up a theme wrapper. Details: YdsThemeProvider.
4. Let the bundler compile CSS Modules
Components import their own *.module.css files. Process those as CSS Modules (including under node_modules). Process every other .css file from this package and from @yleisradio/yds-core as global CSS — token files must not be hashed.
Keep the default key convention. The compiled JavaScript uses a default import (import styles from './Button.module.css') and reads keys such as styles.variant_primary, so do not enable camelCase or other localsConvention transforms.
Vite
Vite compiles CSS Modules from node_modules without configuration. Add vite-css-modules as well: YDS shares styles such as typography through composes, and plain Vite inlines each composed rule into every module that uses it (about 44% more CSS). The plugin puts CSS Modules into Vite's module graph, so each rule is emitted once. YDS Storybook uses the same setup.
npm install --save-dev vite-css-modules
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { patchCssModules } from 'vite-css-modules';
export default defineConfig({
plugins: [patchCssModules(), react()],
// Only if component styles go missing after dependency pre-bundling:
optimizeDeps: {
exclude: ['@yleisradio/yds-components-react'],
},
});
Webpack 5
css-loader must run on this package's CSS. The usual failure is exclude: /node_modules/ on the CSS rule, which drops both the component modules and the token files. On css-loader 7+, set namedExport: false and exportLocalsConvention: 'as-is'.
// webpack.config.js
const path = require('path');
const ydsCss = /node_modules\/@yleisradio\/yds-(components-react|core)/;
module.exports = {
module: {
rules: [
{
test: /\.module\.css$/,
include: [path.resolve(__dirname, 'src'), ydsCss],
use: [
'style-loader', // or MiniCssExtractPlugin.loader
{
loader: 'css-loader',
options: {
modules: {
namedExport: false,
exportLocalsConvention: 'as-is',
},
},
},
],
},
{
// Token files and other global CSS.
test: /\.css$/,
exclude: /\.module\.css$/,
include: [path.resolve(__dirname, 'src'), ydsCss],
use: ['style-loader', 'css-loader'],
},
],
},
};
If you already have equivalent rules for your own src tree, extend their include (or stop excluding node_modules) so both @yleisradio packages match.
Next.js
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ['@yleisradio/yds-components-react'],
};
module.exports = nextConfig;
Also list every dependency that renders YDS 3 components, such as a shared component library. Next.js cannot resolve CSS Modules from untranspiled node_modules code. Import token CSS and utilities.css from pages/_app or the root layout, not from a CSS Module.
Test runners
Components import stylesheets, so the test runner has to handle CSS.
Jest:
// jest.config.js
moduleNameMapper: {
'\\.css$': 'identity-obj-proxy',
},
Vitest — Node's ESM loader cannot process CSS Modules, so inline the YDS packages and skip CSS:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
css: false,
server: {
deps: {
inline: [/@yleisradio\/yds-/],
},
},
},
});
Cascade layer and utilities
Component styles live in @layer yds. An unlayered global reset (* { margin: 0; padding: 0 }) wins over that layer and strips spacing. Breaking changes — Global resets has the layer recipe.
5. Render a component
import { Button } from '@yleisradio/yds-components-react';
<Button variant="primary" text="Click me" />;
If the button has no color or padding, the bundler is not compiling CSS Modules from node_modules, or the token CSS is not on :root. Check DevTools: html should list --yds-color-* variables, and the button's padding should come from a rule labelled @layer yds.
6. Use tokens in your own styles
.card {
background: var(--yds-color-background-variant);
color: var(--yds-color-text-default);
padding: var(--yds-spacing-16);
border-radius: var(--yds-border-radius-medium);
box-shadow: var(--yds-shadow-md);
}
In JavaScript, defaultTheme from @yleisradio/yds-core is the same values, typed, with a light fallback:
import { defaultTheme } from '@yleisradio/yds-core';
defaultTheme.BACKGROUND; // 'var(--yds-color-background, #ffffff)'
Token catalogue: Design tokens.
YDS components include their own focus ring. For your own components, import the utilities.css from the app entry – it contains shared styles for global use.
import '@yleisradio/yds-components-react/utilities.css';
Put yds-use-focus on body (or any wrapper) to draw the ring on :focus-visible inside that subtree. yds-focus does the same for one element.
Without React
React is the primary distribution. Two other ways reuse the same .module.css source. Pick one for a given page — mixing them with each other or with the React components duplicates every rule.
CSS Modules (Vue, Svelte, webpack, Vite, …)
Import individual stylesheets. The bundler hashes class names locally — same contract as React (styles.root, styles.variant_primary).
import styles from '@yleisradio/yds-components-react/Button.module.css';
Deep paths for internals and composite markup:
import styles from '@yleisradio/yds-components-react/styles/components/Button/Button.module.css';
import labelStyles from '@yleisradio/yds-components-react/styles/internal/components/CheckableLabel/CheckableLabel.module.css';
Requirements:
- Design tokens from
@yleisradio/yds-coreon:root(see step 3 above). - Process
*.module.cssas CSS Modules fromnode_modules; keep token CSS global. - Markup and class keys are your responsibility — copy structure from the React component (e.g. Button:
root+size_*+variant_*on the host,TextWrapper/IconWrapperon children). - Class names and component markup are not part of the versioned package API and can change in a minor or patch release. Pin the package version and add guardrails on the application side.
- Composite components may need multiple modules (Checkbox =
Checkbox.module.cssplusCheckableLabel.module.cssunderstyles/internal/…). - Use the imported
stylesobject rather than reconstructing class strings.composes(typography, shared slots) is resolved into the exported map, sostyles.rootmay already be several class names. - Do not import
all.csstogether with CSS Modules.
Naming: CSS class naming conventions.
Matching all.css class names from CSS Modules
The default setup hashes locals. If you want the same stable names as all.css (yds-Button, yds-Button__variant_primary) but only the components you import, keep using CSS Modules and point the bundler’s generateScopedName at YDS files. Do not also import all.css.
This is the right path when you have a bundler and want readable class names in HTML. Unlike all.css, composes is not merged into one class here: it lands on the element as extra class names through the styles export.
The helper ships with the package, as ESM and CJS. Import it — do not retype it. A few modules publish under a different name than their file (BaseRadio.module.css → yds-Radio), and a hand-written yds-${basename} gets those wrong. See Module name vs component name.
Vite — generateScopedName is global. Pass the helper; Vite’s default hash pattern ('[name]__[local]__[hash:base64:5]') is a string interpolator and cannot be mixed with a function:
import { defineConfig } from 'vite';
import { generateScopedName } from '@yleisradio/yds-components-react/scoped-name';
export default defineConfig({
css: {
modules: { generateScopedName },
},
});
That replaces hashing for every CSS Module in the app. Keep Vite’s default if your own modules should stay hashed (the React path). There is no per-package switch.
Webpack 5 — a dedicated rule is cleaner than branching inside getLocalIdent. Keep the hashed rule for your src tree, and add:
const { generateScopedName } = require('@yleisradio/yds-components-react/scoped-name');
{
test: /\.module\.css$/,
include: /node_modules\/@yleisradio\/yds-components-react/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
namedExport: false,
exportLocalsConvention: 'as-is',
getLocalIdent(context, _localIdentName, localName) {
return generateScopedName(localName, context.resourcePath);
},
},
},
},
],
}
Exclude this package from the hashed *.module.css rule (exclude: /@yleisradio\/yds-components-react/) so the two do not both match.
React apps that need microfrontend isolation should keep the default hash. Readable names share the global yds-* namespace, so two YDS versions on one page will clash.
Bundled all.css (CDN, HTML, no bundler)
One global stylesheet with every component's styles under readable class names, for CDN delivery or prototypes.
import '@yleisradio/yds-components-react/styles/all.css';
Load the font and the design tokens first (steps 2 and 3). all.css does not contain tokens.
Write the classes for each prop and element you render, copying the structure from the React component:
<button class="yds-Button yds-Button__size_md yds-Button__variant_primary">
<span class="yds-Button__TextWrapper">Click me</span>
</button>
Shared styles are already part of the component class. yds-Accordion__Title_size_md carries its typography, so there is no separate typography class to add.
What else is in the file:
- Every rule is in a single
@layer yds, same as the React components. See Breaking changes — Global resets if the page has a reset. - The contents of
utilities.css(yds-use-focus,yds-focus,yds-focus-ring,yds-visually-hidden). Do not importutilities.cssseparately. - The body scroll lock used by Modal and BottomSheet.
all.css is not part of the versioned package API. Class names and markup can change in a minor or patch release, so pin the package version and check the class renames when you upgrade.
There is no per-component stylesheet. If the full bundle is too much, use CSS Modules with the generateScopedName config above. Do not import all.css together with CSS Modules or React components — that duplicates styles.
Naming: CSS class naming conventions.
Related
- Breaking changes in React package 3.0 — What 2.x apps must change.
- Using styled-components with YDS 3.0 — If the app keeps styled-components.
- YdsThemeProvider — Alternative to token CSS for a subtree or React-driven theme.