Skip to content

ExpressiveCSS

Browse documentation

Material Design 3 for the web

Build Material Design 3 interfaces from semantic HTML, with light and dark themes and accessible components.

Get started Browse components GitHub

npm install @expressivecss/expressive

Features

Material Design 3 tokens

Color, typography, shape and elevation tokens, with the whole color scheme generated from one source color.

Light and dark themes

Set theme="light" or theme="dark" on the root element, or use auto to follow the operating system.

Semantic HTML

The element is the component: an article is a card and a label wrapping a checkbox is a switch.

Accessible components

Interactive components manage focus, keyboard navigation and ARIA state for you.

No build step required

Link one stylesheet and one script, or import only the components you use from the ES module and Sass entries.

Charts included

Line, column, bar, pie, radar and heatmap charts, each with an accessible data table.

Code demo

This card, its filter chips and its switch are plain HTML. Switch the theme from the app bar to see the tokens change.

Trip settings

Weekend in the mountains

Every color here comes from theme tokens, so the card follows light and dark mode.

<article class="outlined">
  <header>
    <h4>Trip settings</h4>
    <p class="subhead">Weekend in the mountains</p>
  </header>
  <p>Every color here comes from theme tokens, so the card follows light and dark mode.</p>
  <p>
    <input type="checkbox" class="chip-input" id="demo-hiking" checked>
    <label class="chip" for="demo-hiking">
      <span class="material-symbols" aria-hidden="true">check</span>
      Hiking
    </label>
    <input type="checkbox" class="chip-input" id="demo-camping">
    <label class="chip" for="demo-camping">
      <span class="material-symbols" aria-hidden="true">check</span>
      Camping
    </label>
  </p>
  <label class="switch">
    <input type="checkbox" checked>
    Download offline maps
  </label>
  <div class="actions">
    <button type="button" class="text">Cancel</button>
    <button type="button" class="filled">Save</button>
  </div>
</article>

Installation

Expressive comes in two forms. Pick the one that fits your preference and experience. The project is at version 0.16.0 and still growing, so the usual path is to build from the repository.

Expressive

The standard version includes minified and unminified CSS and JavaScript files. Build the project, then copy the files from dist/. This option needs little to no setup. Use it if you are unfamiliar with Sass.

Sass

This version is the source SCSS in src/sass. It gives you more control over which layers to include. It needs a Sass compiler.

From source

Clone the repository and build the compiled CSS and JavaScript:

npm install
npm run build

The compiled assets are written to dist/:

dist/
├── css/
│   ├── expressive.css
│   └── expressive.min.css
├── fonts/
├── js/
│   ├── expressive.cjs
│   ├── expressive.js
│   ├── expressive.min.js
│   ├── expressive.mjs
│   └── modular.mjs
└── types/
NPM

The package name is @expressivecss/expressive. It contains the source files and the compiled CSS and JavaScript files.

npm install @expressivecss/expressive
Yarn

You can also add the package with yarn.

yarn add @expressivecss/expressive

Using a coding agent? AI agents covers the agent skills, the MCP server, and the plain-text docs.

Setup

Project structure

After building, copy the compiled files into your website's directory. It will look something like the tree below.

There are two sets of files. The min files are compressed to reduce load times. Use the minified files in production and the unminified files during development.

MyWebsite/
  |--css/
  |  |--expressive.css
  |  |--expressive.min.css
  |
  |--fonts/
  |  |--material-symbols-outlined.woff2
  |
  |--js/
  |  |--expressive.js
  |  |--expressive.min.js
  |
  |--index.html
HTML setup

Next, link the files in your webpage, as in the example below. Load JavaScript files at the end of the body to reduce page load time.

<!DOCTYPE html>
<html lang="en" theme="light">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="css/expressive.min.css">
  </head>
  <body>
    <script src="js/expressive.min.js"></script>
    <script>
      document.addEventListener('DOMContentLoaded', () => {
        Expressive.AutoInit();
      });
    </script>
  </body>
</html>
Initialize JavaScript

Call AutoInit() after the page has loaded so components such as navigation rails, tooltips, and tabs start themselves. Importing the JavaScript does not call it for you. The import does install shared document behaviors (forms, chips, cards, and a few others). The browser bundle exposes the framework as the global Expressive object.

AutoInit() scans document.body by default. Pass a container to limit the scan, or add the no-autoinit class to an element you want to initialize yourself.

Expressive.AutoInit();

Expressive.AutoInit(document.querySelector('#app'), {
  Tooltip: { position: 'top' }
});
ES modules

Import the framework or individual components from the module build:

import { AutoInit, Tooltip } from './js/expressive.mjs';

AutoInit();

const element = document.querySelector('.custom-tooltip');
Tooltip.init(element, { position: 'top' });
Themes

Set the theme attribute on the root element. light and dark lock the scheme. auto, or no attribute at all, follows the OS.

<html lang="en" theme="auto">
document.documentElement.setAttribute('theme', 'dark');

Templates

Browse layout templates

Basic starter

Download this HTML file as index.html and put the framework's dist/ directory beside it. It includes an app bar, responsive cards, a tooltip, and a theme that follows your system settings. You do not need a bundler.

Download starter HTML Preview starter

From a built repository checkout, create a separate starter directory:

mkdir my-site
cp docs/public/starter.html my-site/index.html
cp -R dist my-site/dist

If you installed the npm package, copy its built assets instead:

cp -R node_modules/@expressivecss/expressive/dist my-site/dist

Keep the css/, js/, and fonts/ directories together.

my-site/
├── index.html
└── dist/
    ├── css/
    ├── js/
    └── fonts/

Open my-site/index.html in your browser. Edit the title and content to start your project, or copy the complete template below.

<!doctype html>
<html lang="en" theme="auto">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="A starter page for your ExpressiveCSS project.">
    <title>My project</title>
    <!-- Keep dist/fonts next to dist/css so the bundled fonts load. -->
    <link rel="stylesheet" href="dist/css/expressive.min.css">
  </head>
  <body>
    <header>
      <nav aria-label="Main">
        <h2>My project</h2>
        <menu>
          <li><a href="https://www.expressivecss.com/">Documentation</a></li>
        </menu>
      </nav>
    </header>

    <main class="container py-5">
      <section aria-labelledby="welcome-title" class="mb-5">
        <h1 id="welcome-title">Your project starts here</h1>
        <p class="flow-text">Replace this content with your own. ExpressiveCSS handles the layout, typography, and theme.</p>
        <a class="button filled tooltipped" href="https://www.expressivecss.com/"
           data-tooltip="Read the setup guide and browse components.">Start building</a>
      </section>

      <!-- Full-width cards on phones, two columns from 600px. -->
      <div class="row">
        <div class="s12 m6">
          <article class="outlined">
            <h2>Your content</h2>
            <p>Use this card for a feature, a project summary, or a next step.</p>
            <div class="actions">
              <a class="button outlined" href="https://www.expressivecss.com/grid.html">Explore layouts</a>
            </div>
          </article>
        </div>
        <div class="s12 m6">
          <article class="outlined">
            <h2>Make it yours</h2>
            <p>The page follows your system theme. Set the root theme attribute to light or dark to choose a fixed scheme.</p>
            <div class="actions">
              <a class="button text" href="https://www.expressivecss.com/themes.html">Customize the theme</a>
            </div>
          </article>
        </div>
      </div>
    </main>

    <footer class="container py-3">
      <p>My project</p>
    </footer>

    <script src="dist/js/expressive.min.js"></script>
    <script>
      document.addEventListener('DOMContentLoaded', () => {
        Expressive.AutoInit();
      });
    </script>
  </body>
</html>
Layout reference

These two pages show how Expressive structures content. You can copy the markup they use.

Grid

The 12-column CSS Grid, containers, offsets, and responsive layouts. Start here after you link the stylesheet.

Open Grid
Helpers

Alignment, visibility, spacing, truncation, and other single-purpose classes you will use on every page.

Open Helpers

Smaller builds

Use the modular entry with a bundler to include selected JavaScript components. Initialize each component after its markup exists, and destroy instances when you remove the view.

import { Tabs } from '@expressivecss/expressive/modular';
const tabs = Tabs.init(document.querySelector('.tabs'));

This entry installs no document behaviors. Call each behavior your page needs once, such as Forms.Init() or Dialogs.Init(). The root import keeps its existing initialization. AutoInit is still available and includes the full component registry.

Select Sass partials with the custom entry. With Sass's Node package importer, compile with sass --pkg-importer=node app.scss app.css.

@use "pkg:@expressivecss/expressive/scss/custom" with (
  $components: ("icons-material-design", "tabs"),
  $utilities: ()
);

Tokens and base styles stay included. Lists load in the order written. null includes the full group, and () omits it. Include supporting partials for the features you use. The default configuration produces the same CSS as the full entry.

For FormSelect and Datepicker, select icons-material-design, buttons, menu, forms, and datepicker, in that order. Initialize both components explicitly.

To supply smaller icon fonts, set $expressive-include-fonts: false and provide your own font faces, including text fonts. Your subset must include icons from dynamic content and generated controls. Keep full fonts if icon names are unrestricted.

The README contains the initialization table, complete Sass recipes, and font declarations.

Sass setup

You only need this section if you chose the Sass sources.

Compiling Sass

Besides compiled CSS, the repository contains many .scss files with the styles of individual layers and components. Browsers cannot read Sass, so compile src/sass/expressive.scss into a regular CSS file. Then link the compiled file in your HTML page.

From the repository, the npm script does that for you:

npm run build:css

In another Sass project, use the framework's Sass entry point:

@use "@expressivecss/expressive/src/sass/expressive";

Inside this repository, the entry point is src/sass/expressive.scss.

MyWebsite/
|--css/
|  |--expressive.css <-- compiled from scss/expressive.scss
|
|--js/
|  |--expressive.js
|
|--scss/
|  |--expressive.scss
|  |--abstracts/
|  |--tokens/
|  |--utilities/
|  |--base/
|  |--components/
|
|--index.html

No matching pages

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.

    Default source: Mist Iris.

    ExpressiveCSS presets: Lagoon, Harbor, Breeze, Cobalt, Iris.

    Bold

    Soft

    Mist

    Glow