@skagerakenergi/design
v0.23.0
Published
Skagerak Energi design system — M3 component library (CSS only)
Readme
@skagerakenergi/design
Material Design 3 (M3) component library for Skagerak Energi.
This package ships CSS only:
- M3 token files
- Per-component CSS files
- One convenience bundle (
index.css)
Install
pnpm add @skagerakenergi/designQuick start (all styles)
Use index.css to load token files and all components.
@import '@skagerakenergi/design/index.css';<button class="se-button se-button--filled">Save</button>This is the fastest option when you want all components and automatic theme handling.
Recommended import (theme + selected components)
Load auto theme, system tokens, and only the components you use.
@import '@skagerakenergi/design/tokens/theme-auto.css';
@import '@skagerakenergi/design/tokens/system.css';
@import '@skagerakenergi/design/button.css';
@import '@skagerakenergi/design/icon-button.css';Use this mode to keep bundle size smaller by importing only the components you use.
Import only a single component
@import '@skagerakenergi/design/tokens/theme-auto.css';
@import '@skagerakenergi/design/tokens/system.css';
@import '@skagerakenergi/design/chips.css';<div class="se-chip-set" role="listbox" aria-label="Filter options">
<button class="se-chip se-chip--filter" aria-pressed="false">Option</button>
</div>Theming
The package supports six theme variants:
lightdarklight-medium-contrastdark-medium-contrastlight-high-contrastdark-high-contrast
Automatic theming (recommended)
theme-auto.css sets light tokens by default and automatically adapts to user OS settings for dark mode and high contrast.
@import '@skagerakenergi/design/tokens/theme-auto.css';
@import '@skagerakenergi/design/tokens/system.css';theme-auto.css includes:
- Light defaults on
:root - Automatic dark mode via
prefers-color-scheme: dark - Automatic high contrast via
prefers-contrast: more - Manual override classes (
.light,.dark,.light-medium-contrast,.dark-medium-contrast,.light-high-contrast,.dark-high-contrast)
Manual theme swapping
Import explicit scheme files and set a theme class yourself.
@import '@skagerakenergi/design/tokens/light.css';
@import '@skagerakenergi/design/tokens/dark.css';
@import '@skagerakenergi/design/tokens/light-mc.css';
@import '@skagerakenergi/design/tokens/dark-mc.css';
@import '@skagerakenergi/design/tokens/light-hc.css';
@import '@skagerakenergi/design/tokens/dark-hc.css';
@import '@skagerakenergi/design/tokens/system.css';
@import '@skagerakenergi/design/button.css';Set a theme class on any ancestor element (html, body, or a section wrapper):
<body class="dark">
<button class="se-button se-button--filled">Save</button>
</body>Swap class at runtime in your app to change theme.
Bring your own tokens (custom theme)
Skip library color theme files and provide your own --md-sys-color-* values.
/* Your app token file */
:root {
--md-sys-color-primary: #1976d2;
--md-sys-color-on-primary: #ffffff;
--md-sys-color-surface: #fdfcff;
--md-sys-color-on-surface: #1a1c1e;
/* Provide the full M3 system color set used by your components */
}
/* Library imports */
@import '@skagerakenergi/design/tokens/system.css';
@import '@skagerakenergi/design/button.css';Use this when you want your own brand palette while keeping the same component CSS API.
Bring your own tokens + auto-engine (OS preferences without custom media queries)
Use this repository's auto-engine to generate a theme-auto.css from your own Material Theme Builder exports, so you keep automatic dark/high-contrast behavior.
Important:
@skagerakenergi/design/tokens/theme-auto.cssalways points to the package's built-in token set.- To use your own token set, import your own generated
theme-auto.cssfrom your codebase.
Supported input filenames:
light.cssdark.csslight-mc.cssdark-mc.csslight-hc.cssdark-hc.css
Minimal setup (directory input):
pnpm build:theme-auto -- --source-dir ./brand-tokensThis generates tokens/theme-auto.css using your files when present and falls back to the library defaults for missing variants.
Strict mode (require all 6 custom variants):
pnpm build:theme-auto -- --source-dir ./brand-tokens --strictPer-file override example:
pnpm build:theme-auto -- --source-dir ./brand-tokens --dark ./brand/dark.css --out ./tokens/theme-auto.cssThen import as usual:
@import './tokens/theme-auto.css';
@import '@skagerakenergi/design/tokens/system.css';
@import '@skagerakenergi/design/button.css';In short:
- Import package
theme-auto.csswhen you want the package defaults. - Import local
theme-auto.csswhen you want your own upstream tokens.
Icons
Components that display icons expect Material Symbols Outlined to be loaded. The icon font is not bundled — load it yourself so you control which icons are included.
Google Fonts (recommended)
Add a <link> in your <head>. Use the &icon_names= parameter to include only the icons you need (keeps the download small):
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:[email protected]&icon_names=home,search,menu,close&display=block" rel="stylesheet" />[email protected]— enables the filled/outlined toggle used by navigation components&display=block— prevents a flash of unstyled ligature text&icon_names=— comma-separated list of the icons your app uses
Self-hosting
Download the WOFF2 from google/material-design-icons and add:
@font-face {
font-family: 'Material Symbols Outlined';
font-style: normal;
src: url('./material-symbols-outlined.woff2') format('woff2');
}
.material-symbols-outlined {
font-family: 'Material Symbols Outlined';
font-weight: normal;
font-style: normal;
font-size: 24px;
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
font-variation-settings: 'FILL' 0;
}Using icons in markup
<span class="material-symbols-outlined" aria-hidden="true">home</span>See .specs/icons.md for the full icon catalog and integration details.
Usage patterns
All components
@import '@skagerakenergi/design/index.css';Selected components
@import '@skagerakenergi/design/tokens/theme-auto.css';
@import '@skagerakenergi/design/tokens/system.css';
@import '@skagerakenergi/design/button.css';
@import '@skagerakenergi/design/card.css';
@import '@skagerakenergi/design/text-field.css';Manual theme + selected components
@import '@skagerakenergi/design/tokens/light.css';
@import '@skagerakenergi/design/tokens/dark.css';
@import '@skagerakenergi/design/tokens/system.css';
@import '@skagerakenergi/design/navigation-bar.css';
@import '@skagerakenergi/design/switch.css';Custom tokens + selected components
@import './my-theme.css'; /* provides --md-sys-color-* */
@import '@skagerakenergi/design/tokens/system.css';
@import '@skagerakenergi/design/text-field.css';Custom auto-theme + selected components (upstream token files)
@import './tokens/theme-auto.css'; /* generated from your own Theme Builder CSS files */
@import '@skagerakenergi/design/tokens/system.css';
@import '@skagerakenergi/design/text-field.css';Available imports
Token files:
@skagerakenergi/design/tokens/theme-auto.css@skagerakenergi/design/tokens/system.css@skagerakenergi/design/tokens/light.css@skagerakenergi/design/tokens/dark.css@skagerakenergi/design/tokens/light-mc.css@skagerakenergi/design/tokens/dark-mc.css@skagerakenergi/design/tokens/light-hc.css@skagerakenergi/design/tokens/dark-hc.css
Component files:
app-bar.css,badge.css,button.css,button-group.css,card.csscheckbox.css,chips.css,divider.css,extended-fab.css,fab.cssicon-button.css,list.css,loading-indicator.css,menu.css,navigation-bar.cssnavigation-rail.css,progress-indicator.css,radio.css,snackbar.csssplit-button.css,switch.css,tabs.css,text-field.css,toolbar.csstooltip.csslayout.css— spacing utilities, breakpoints, canonical layouts (list-detail, supporting pane, feed, split-pane), column grids (.se-columns-2/3/4), auto-fill grid (.se-grid), flex containers (.se-row,.se-column), flex/alignment utilities
Optional JavaScript modules (overlay components):
menu.js— keyboard navigation + focus management for Menu (ESM, TypeScript declarations atmenu.d.ts)
Loading indicator shape asset (avoids inlining the large SVG path data):
loading-indicator-shapes.svg— import with?rawin Vite-based projectsloading-indicator-shapes.js— ESM export (loadingIndicatorShapesSvg: string) for non-Vite environments
Brand components (not part of M3 — use Skagerak brand palette, no M3 tokens required):
brand/logo.css— Skagerak group logo (sk-logo,sk-logo__mark,sk-logo__brand,sk-logo__subsidiary)brand/skagerak_globe.svg— monochrome globe emblem (fill="currentColor") for inline embedding in the flat logo variant
Figma Code Connect
This project uses Figma Code Connect to link Figma component instances to the corresponding HTML/CSS snippets so designers see real code in the Figma Dev Mode inspector.
Prerequisites
Figma access token — create one at Figma → Settings → Personal access tokens with the minimum scopes used by Code Connect publishing:
file_code_connect:write(Write and change component code)file_dev_resources:writefile_content:readfile_metadata:read
In the Figma token UI, these are shown under Development and Files with human-readable labels. Use the scope IDs above as the source of truth when documenting automation.
Export the token as
FIGMA_ACCESS_TOKEN:export FIGMA_ACCESS_TOKEN=figd_xxxxx@figma/code-connectis already a dev dependency (seepackage.json).
Project config
figma.config.json at the repo root tells the CLI where to find Code Connect files:
{
"codeConnect": {
"include": ["src/components/**/*.figma.ts"],
"exclude": ["node_modules/**", "dist/**", "storybook-static/**"],
"label": "HTML"
}
}include— glob for Code Connect source files.label— the language label shown in Figma Dev Mode (e.g."HTML").
File naming convention
Each component that has a Code Connect mapping gets a *.figma.ts file alongside its CSS and Storybook files:
src/components/button/
button.css
button.stories.ts
button.figma.ts ← Code Connect fileAnatomy of a Code Connect file
A Code Connect file imports the HTML helpers and calls figma.connect() once per Figma component set (node). Here is a minimal example:
import figma, { html } from '@figma/code-connect/html'
figma.connect(
// 1. Figma node URL — right-click a component set in Figma → "Copy link to selection"
'https://www.figma.com/design/<FILE_KEY>/<FILE_NAME>?node-id=<NODE_ID>',
{
// 2. Props — map Figma variant properties to code values
props: {
className: figma.className([
'se-button',
'se-button--filled',
figma.enum('Size', {
XSmall: 'se-button--xs',
Medium: 'se-button--md',
Large: 'se-button--lg',
XLarge: 'se-button--xl'
})
]),
label: figma.string('Label text'),
icon: figma.boolean('Show icon', {
true: html`<span class="se-button__icon material-symbols-outlined">add</span>`,
false: undefined
})
},
// 3. Example — the HTML snippet shown in Figma Dev Mode
example: ({ className, label, icon }) => html`\
<button class="${className}">
${icon}
${label}
</button>`,
// 4. Imports — CSS import hint displayed alongside the snippet
imports: ["@import '@skagerakenergi/design/button.css';"]
}
)Key prop helpers
| Helper | Purpose | Example |
|---|---|---|
| figma.className([...]) | Build a class string from static values and variant mappings | figma.className(['se-button', figma.enum('Style', { Filled: 'se-button--filled' })]) |
| figma.enum('Prop', map) | Map a Figma enum property to code values | figma.enum('Size', { Small: 'sm', Large: 'lg' }) |
| figma.string('Prop') | Pull a text/string property value straight through | figma.string('Label text') |
| figma.boolean('Prop', { true, false }) | Map a boolean toggle to different code outputs | figma.boolean('Show icon', { true: html\...`, false: undefined })|
|figma.instance('Prop')| Render a nested Figma instance with its own Code Connect |figma.instance('Leading icon')` |
Writing a new Code Connect file step by step
Identify the Figma component set. Open the Figma file, navigate to the component, right-click the top-level component set, and choose Copy link to selection. The URL contains the
node-idyou need.Create the file. Add
src/components/<name>/<name>.figma.ts.Map props. Open the component's property panel in Figma to see every variant property (e.g.
Size,Type,Style,Show icon). Map each one using the helpers above.Write the
examplefunction. Return anhtmltagged template that produces the same markup the component's CSS expects. Reference the mapped props by name.Add
imports. List the CSS@importstatement(s) a consumer needs.Handle multiple variants. If the Figma file splits a component into separate component sets (e.g. filled button vs outlined button), add a separate
figma.connect()call for each and hardcode the modifier class. See button.figma.ts for a real example.Validate locally.
# Parse all Code Connect files and report errors without publishing pnpm figma:publish --dry-runPublish to Figma.
pnpm figma:publishThis pushes the snippets to Figma so they appear in Dev Mode for the linked components.
Unpublish (remove all snippets).
pnpm figma:unpublish
Tips
- One
figma.connect()per component set node. If a component has five style variants as separate nodes in Figma, write five calls. - Prop names are case-sensitive and must match the Figma property name exactly (e.g.
'Label text', not'label text'). - Use
htmltagged templates (from@figma/code-connect/html) so Figma renders the snippet with proper syntax highlighting. - Backslash after the opening backtick (
html\\`) prevents a leading newline in the rendered snippet. importsis an array of strings — each string is shown as-is in the Figma UI. Use the package-level import path (@skagerakenergi/design/...) so consumers see the right import.- Keep snippets minimal — show the most common usage; don't try to cover every edge case.
- Refer to existing files for patterns: button.figma.ts, icon-button.figma.ts.
