Navigation

Integrations

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

  1. #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
  2. #Keep a CSS source entry

    The installer creates src/index.css and registers the Vite integration. Keep definitions in this source file:

    src/index.css
    @import '@master/css';

    The component below uses Vite's ?inline import 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 produce Invalid base URL when the production component loads. Merge this into the existing Vite configuration, keeping other plugins and registering Master CSS once:

    vite.config.ts
    import { defineConfig } from 'vite'import masterCSS from '@master/css-vite'export default defineConfig({  base: './',  plugins: [masterCSS()]})
  3. #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 to unsafeCSS. The installer preserves the standard component’s existing styles when adding this stylesheet.

    src/my-element.ts
    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>`  }}
  4. #Start using Master CSS

    Keep the component module imported by the application entry, then place its registered element in the page:

    src/main.ts
    import './my-element'
    <my-element></my-element>

    The existing index.html must load src/main.ts as a module. Run npm run dev; Lit creates the paragraph inside the element's shadow root.

  5. Check styles inside a shadow root

    The preview loads the generated stylesheet inside a real shadow root, alongside the paragraph.

#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 preview

Inspect 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 --yes

The 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:

src/my-element.ts
/// <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')