Skip to content

ExpressiveCSS

Browse documentation

Auto Init

Initialize every registered component with one function call.

View as Markdown

Auto Init starts all of the registered Expressive components with a single call. The IIFE bundle exposes it as Expressive.AutoInit.

Call AutoInit() yourself after the DOM is ready. Importing the JavaScript installs a few document-level behaviors (Forms, Chips, Slider, Cards, ExpandingCard, Dialogs, BottomSheets, and SideSheets), but it does not call AutoInit() for you. This documentation site calls it in docs.js on DOMContentLoaded.

Initialization

document.addEventListener('DOMContentLoaded', function() {
  Expressive.AutoInit();
});

Or pass a root element and per-component options. The option keys are the component names from the table below.

Expressive.AutoInit(document.body, {
  Menu: {
    // pass options for Menu here
  },
  FloatingActionButton: {
    // pass options for FloatingActionButton here
  }
});

From the module build:

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

AutoInit();

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

Component init() methods also accept a NodeList or HTMLCollection and return an array of instances. An empty collection returns an empty array.

Options

AutoInit(context, options, settings) takes three arguments:

Name Type Default Description
context Element document.body DOM element or shadow root to search within for components.
options Object {} Options for each component, keyed by name. See that component's page for its own options.
settings Object {} { watch: true } keeps starting components in markup added to context later. See Markup added later.

You can call AutoInit() again on the same elements. Each component's constructor destroys any existing instance on that element before creating a new one.

Options in markup

To give an element its own options for AutoInit(), add a data-name-options attribute that holds a JSON object. The name is the component's name from the table below in kebab case. The keys are its option names as written in script.

<a class="button menu-trigger" data-target="actions"
   data-menu-options='{"closeOnClick": false, "placement": "top"}'>Actions</a>

<a class="button tooltipped" data-tooltip="Saved"
   data-tooltip-options='{"enterDelay": 300}'>Save</a>

<select data-form-select-options='{"classes": "wide"}'>...</select>
  • Menu reads data-menu-options, FormSelect reads data-form-select-options, and FloatingActionButton reads data-floating-action-button-options. The name comes from the table, not the class, so a .date-picker reads data-datepicker-options. An element that is both a tooltip and a menu trigger reads each attribute for its own component.
  • Options passed to AutoInit(context, options) win for the keys they set. A nested object such as i18n counts as one key. Tooltip reads its own data-tooltip, data-tooltip-id and data-position again each time it opens, so those win over both. Set the position, and Menu's contextMenu (data-context-menu), in one place.
  • Markup can set an option whose default is a string, number, boolean, array or plain object, with a value of the same kind. AutoInit() checks keys inside an object such as i18n the same way. If it rejects one, the whole object keeps its default. An object whose default is empty, such as FormSelect's menuOptions, accepts no keys from markup. When the default is a non-empty array, the items need the kind of its first item. Callbacks, elements and options whose default is null cannot come from markup; pass those from script. Autocomplete's allowUnsafeHTML and the data lists of Chips and Autocomplete are script-only too.
  • AutoInit() treats the attribute as untrusted. It skips invalid JSON, an unknown key or a value of the wrong kind with a console warning, and that option keeps its default. A value that passes these checks but still makes a component throw stops AutoInit(), as the same value passed from script would.
  • Only AutoInit() reads the attribute. A direct init() call uses the options it is given. Menus inside a .menubar take their options from Menubar's menuOptions, so data-menu-options has no effect on them.

Markup added later

AutoInit() scans context once. To keep watching it, pass { watch: true } as the third argument. Components then start in markup added later, and AutoInit() destroys them when their markup is removed. The call returns a function that stops watching.

const stop = Expressive.AutoInit(document.body, {}, { watch: true });

// Starts a Menu once the observer sees the trigger, without calling init.
document.querySelector('main').insertAdjacentHTML('beforeend', `
  <a class="button menu-trigger" data-target="more">More</a>
  <menu id="more"><li><a href="#!">Share</a></li></menu>`);

// In your view's teardown. Components already started keep running.
function unmount() {
  stop();
}
  • Added markup starts the same way the first scan does, with the same options, data-name-options attributes and no-autoinit opt-out. The added element is checked as well as its descendants. Components start in a later microtask, when the browser reports the change, not during the call that adds the markup.
  • An element that already has an instance keeps it. Starting a component yourself before adding its element works, and components that build their own markup, such as the Datepicker's month and year selects, keep the instances they made.
  • Removed markup destroys the instances AutoInit() would have started. Elements with no-autoinit are left to your code. An element moved elsewhere in the page keeps its instance. One that is removed and added back later starts again.
  • Only added and removed elements count. Adding tooltipped to an element already in the page does not start a Tooltip; call init for that.
  • Markup inserted at runtime becomes live components. If you insert user content, sanitize it first and drop component classes and data-name-options attributes you don't want started.
  • Watch mode does not enter shadow roots. To watch markup inside one, call AutoInit(shadowRoot, options, { watch: true }) for that root.
  • Call it once per root. A component that throws while starting stops the rest of that batch, the same way it stops AutoInit(). The watcher still handles later changes.

stop() first handles changes the browser has not reported yet, so markup added just before the call still starts. Without watch, AutoInit() returns a function that does nothing.

Components

These are the components AutoInit() starts, and the selector each one claims. Elements that also have no-autoinit are skipped.

Name Selector
AppBarheader.medium, header.large, header:has(> :is(nav:not(.tabs, .navigation-bar, .navigation-rail), .bar) > .search-bar)
Autocomplete.autocomplete
ButtonGroup.button-group
Cardsarticle:has(> aside[id]:not([id=""])):not(:has(> aside ~ aside)):has(> button.card-reveal-trigger[type="button"], > :not(aside) button.card-reveal-trigger[type="button"])
ExpandingCardarticle.expanding-card:has(> dialog.expanding-card-dialog)
Carousel.carousel
Chips.chips
Confetti.confetti
Particlescanvas.particles
Keypad.keypad, .keyboard
ColorPicker.color-picker
BarChart.bar-chart
PieChart.pie-chart
HeatmapChart.heatmap-chart
RadarChart.radar-chart
RadialChart.radial-chart
MixedChart.mixed-chart
ScatterChart.scatter-chart
FunnelChart.funnel-chart
SankeyChart.sankey-chart
TreemapChart.treemap-chart
ColumnChart.column-chart
CommandPalettedialog.command-palette
Questionnaireform.questionnaire
Datepicker.date-picker
LineChart.line-chart
Menu.menu-trigger
MessageScroller.message-scroller
PullToRefresh.pull-to-refresh
LoadingOverlay.loading-overlay
SwipeActions.list.swipeable
InputMaskinput[data-mask] of type text, tel, search, url or password
ShowMore.show-more
CurrencyInputinput[data-currency] of type text
Lightbox.lightboxed
NavigationRail.navigation-rail
ScrollSpy.scrollspy
BackToTop.back-to-top
FormSelectselect without data-tree
TreeSelectselect[data-tree]
Cascader.cascader
Tabs.tabs
Timepicker.time-picker
Tooltip.tooltipped
HoverCard.hover-card-trigger
FloatingActionButton.fab-menu
Menubar.menubar

Snackbar, CharacterCounter, and Slider stay out of this table. Slider still starts itself when the bundle loads. Forms, Chips, Cards, ExpandingCard, Dialogs, BottomSheets, and SideSheets also run an import-time Init(). Chips, Cards, and ExpandingCard appear in the table as well so a later AutoInit() can pick up elements added after load.

Ignoring elements

To make AutoInit() skip an element, add the class no-autoinit. Initialize that element yourself with the component's init method when you need options other than the defaults.

<a class="button tooltipped no-autoinit" data-tooltip="Hi">Hover</a>

Expressive.Tooltip.init(
  document.querySelector('.tooltipped.no-autoinit'),
  { position: 'top' }
);

Events

Menu, Lightbox, NavigationRail, Cards and ExpandingCard fire DOM events from the element they were initialized on. expressive:open and expressive:close fire when the state changes, and expressive:opened and expressive:closed when the motion ends. Cards and ExpandingCard fire only expressive:open and expressive:close.

Each event comes right after the matching on… callback, which still works. The events bubble and cross shadow roots, so one listener on the document sees every component. Events from nested components bubble too, so check event.target. Closing a Menu or Lightbox from its expressive:open listener stops it from opening.

document.addEventListener('expressive:open', (event) => {
  if (event.target.matches('.menu-trigger')) analytics.track('menu-open');
});

Choices and changes carry what was chosen in event.detail:

EventFired bydetail
expressive:changeTabs, Carousel{ panel }, { item, dragged }
expressive:selectMenu, Datepicker, Timepicker{ item }, { date }, { hours, minutes }
expressive:confirm, expressive:cancelDatepicker, Timepickernone
expressive:autocompleteAutocomplete{ values }
expressive:createAutocomplete, from its "Add" row{ text }
expressive:chip-add, chip-select, chip-deleteChips{ chip }
expressive:changeButtonGroup, Menu checkbox and radio items, ScrollSpy (from the section), Tour{ button, pressed }, { item, checked }, { link }, { step, index }
expressive:open, expressive:closeTooltip, HoverCard, Snackbar, Tour, ShowMoreTour's close: { completed }
expressive:action, expressive:closedSnackbarnone
expressive:copyCopy buttons{ text, copied }
expressive:refreshPull-to-refresh{ waitUntil }
expressive:swipetoggleSwipe actions{ row, open }
expressive:transferTransfer list, from the container{ rows, from, to }
expressive:change, expressive:sortData tables, from the wrapper{ selected }, { header, column, direction }; sort is cancelable

An Autocomplete or Select input hosts a Menu, so it also fires that Menu's expressive:open, expressive:close and expressive:select. A callback that changes the choice again, such as an onShow that selects another tab, leaves only the event for the final choice.

Tooltip fires expressive:open and expressive:close when it shows and hides, after its delays, and so does a hover card, from its link. A snackbar fires its events from the snackbar itself. expressive:closed comes just before it leaves the page. Tour's expressive:close says whether the Done button finished it.

Alpine.js

AutoInit() scans the page once, so it misses anything Alpine renders later with x-if or x-for. Watch mode starts and destroys that markup with the options of the AutoInit() call and its data-name-options attributes. To pass options from Alpine expressions instead, register a directive. Alpine runs it each time it creates the element and calls cleanup when it removes it, so every instance gets destroyed with its element.

document.addEventListener('alpine:init', () => {
  Alpine.directive('expressive', (el, { value, expression }, { evaluate, cleanup }) => {
    // HTML lowercases attribute names, so match the export name the same way.
    const name = Object.keys(Expressive).find((key) => key.toLowerCase() === value);
    const instance = Expressive[name].init(el, expression ? evaluate(expression) : {});
    cleanup(() => instance.destroy());
  });
});

Write the component name after the colon and pass options as the expression. Add no-autoinit so a later AutoInit() call leaves the element alone.

<a class="button tooltipped no-autoinit" data-tooltip="Hi"
   x-expressive:tooltip="{ position: 'top' }">Hover</a>

Select, Autocomplete and Datepicker fire a native change event when the user picks a value, so x-model picks it up. When your code sets the value, the visible field stays stale until you call refresh(). Do the same after adding or removing options. Put the select in its own .field so the template root stays the element Alpine created.

<template x-if="open">
  <div class="field">
    <select id="letter" class="no-autoinit" x-model="letter" x-expressive:formselect
      x-effect="letter; $nextTick(() => $el.Expressive_FormSelect?.refresh())">
      <option value="a">Alpha</option>
      <option value="b">Beta</option>
    </select>
    <label for="letter">Letter</label>
  </div>
</template>

Component events bubble, so Alpine listens to them like any other: @expressive:change="tab = $event.detail.panel.id". Dialogs and sheets are native <dialog> elements. Open one with $refs.dialog.showModal().

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