Set up Master CSS in Lit
Style Lit shadow roots with static CSS or a browser runtime that follows the component lifecycle.
On this page
Lit components normally render into a shadow root. Document styles cannot reach those elements, so deliver the generated CSS inside each component's shadow tree.
#Installation
#Add Master CSS
In an existing Lit project built with Vite, run the installer from the project root. Lit uses Vite static mode by default. Keep complete class names in source; use explicit runtime mode if classes must be generated from new names in the browser.
npm create @master/css@2.0.0 -- --framework lit --yes#Keep a CSS source entry
The installer creates
src/index.cssand registers the Vite integration. Keep definitions in this source file:@import '@master/css';The component below uses Vite's
?inlineimport to receive processed CSS text. A normal document-level stylesheet import cannot style the inside of a shadow root.For the current inline-CSS delivery, use a relative Vite
base. A root-relative asset URL can produceInvalid base URLwhen the production component loads. Merge this into the existing Vite configuration, keeping other plugins and registering Master CSS once:import { defineConfig } from 'vite'import masterCSS from '@master/css-vite'export default defineConfig({ base: './', plugins: [masterCSS()]})#Include styles in the shadow root
Import the generated stylesheet as text and include it with Lit’s
unsafeCSS. The CSS comes from your build input; do not pass untrusted user text tounsafeCSS. The installer preserves the standard component’s existing styles when adding this stylesheet.import { LitElement, html, unsafeCSS } from 'lit'import { customElement } from 'lit/decorators.js'import masterCSSStyles from './index.css?inline'@customElement('my-element')export class MyElement extends LitElement { static styles = [unsafeCSS(masterCSSStyles)] render() { return html`<p id="master-check" class="w-fit m(0) p-md b-solid b(1) b-line-divider r-md text-sm font-regular bg-surface-raised fg-text-strong">Hello Lit</p>` }}#Start using Master CSS
Keep the component module imported by the application entry, then place its registered element in the page:
import './my-element'<my-element></my-element>The existing
index.htmlmust loadsrc/main.tsas a module. Runnpm run dev; Lit creates the paragraph inside the element's shadow root.
Check styles inside a shadow root
#Check the shadow stylesheet
Change p-md to p-lg in the Lit template. Its padding should grow from 1rem to 1.5rem; restore it before checking the production build:
npm run buildnpm run previewInspect the paragraph inside <my-element> in browser developer tools. It should have 1rem padding, a raised surface and rounded corners. If only document-level elements are styled, check the ?inline import and static styles inside the component.
Static mode needs no Master CSS browser runtime. Lit still needs JavaScript to create this client-rendered component.
Generate new classes inside the shadow root
For class names first introduced in the browser, run the installer with runtime mode:
npm create @master/css@2.0.0 -- --framework lit --mode runtime --yesThe installer adds the runtime decorator to the standard src/my-element.ts component and sets masterCSS({ mode: 'runtime' }) in Vite. Keep the relative base above. Other components need the same setup. Load the CSS text before the runtime stylesheet so the layer order is established before generated rules:
/// <reference types="@master/css/client" />import { LitElement, html } from 'lit'import { customElement } from 'lit/decorators.js'import { withMasterCSSRuntime } from '@master/css-runtime'import program from 'virtual:master-css-program'import emittedGlobals from 'virtual:master-css-emitted-globals'import masterCSSStyles from './index.css?inline'@customElement('my-element')@withMasterCSSRuntime({ program, emittedGlobals })export class MyElement extends LitElement { protected createRenderRoot() { const root = super.createRenderRoot() const styles = document.createElement('style') styles.textContent = masterCSSStyles root.append(styles) return root } render() { return html`<p id="master-check" class="w-fit m(0) p-md b-solid b(1) b-line-divider r-md text-sm font-regular bg-surface-raised fg-text-strong">Hello Lit</p>` }}This example inserts the base stylesheet as a <style> element before the runtime starts. Lit’s static styles uses adopted stylesheets, which the CSSOM stylesheet order places after ordinary <style> elements; putting the layer-order declaration there can place the base layer after the runtime’s utilities.
Each decorated component starts a runtime for its own shadow root and disposes it when disconnected. A document-level runtime cannot observe classes inside that root.
After the component appears, run this in the browser console. The paragraph should gain an underline without rebuilding:
document.querySelector('my-element').shadowRoot .querySelector('#master-check').classList.add('underline')
