Choose how to deliver CSS
Pick the CSS delivery that fits: one shared sheet for utility classes, or per-component styles for rules one component owns. The table decides it; the linked pages do it.
Decide in one table
Section titled “Decide in one table”A shared stylesheet is one CSSStyleSheet object adopted by many shadow
roots. The top three rows deliver one; the bottom two give each component its
own copy.
| Shape | Use when | What an edit does | Page |
|---|---|---|---|
?css-sheet |
many components share a sheet and you want it cached as its own .css asset |
restyles every adopter in place, no re-render | ?css-sheet |
urlSheet() |
the same, but you need a custom fetch or code that runs without this plugin | restyles every adopter in place, no re-render | urlSheet() |
?raw into one shared module |
many components share a sheet and first paint must not flash | restyles every adopter in place, no re-render | Component styles |
?hmr-url <link> |
rules one component owns, kept as a real .css asset |
re-renders that component | Component styles |
?raw or ?inline + unsafeCSS |
rules one component owns, shipped in the JS chunk | re-renders that component | Component styles |
A re-render is cheap for one component’s own rules and expensive for a layer every component adopts. CSS delivery has the measurements behind that.
Shared sheet for Tailwind or UnoCSS
Section titled “Shared sheet for Tailwind or UnoCSS”A generated utility sheet is the case the top rows exist for. Every component adopts the same object, so a regenerated file restyles the whole app without re-rendering anything.
Choose by where the bytes should live. ?css-sheet and urlSheet() keep the
CSS as a separate, content-hashed .css asset, at the cost of FOUC: a brief
flash of unstyled content while the stylesheet is still loading. The inline
shared module ships the bytes in a JS chunk. There is no fetch and no flash,
but the CSS is not cacheable on its own.
Compose a shared sheet with component styles
Section titled “Compose a shared sheet with component styles”static styles takes an array, so the two shapes stack. Put the shared sheet
first and the component’s own css block after it, and the later rules win at
equal specificity.
import {LitElement, html} from 'lit';import {customElement, property} from 'lit/decorators.js';import utilitySheet from './hmr-utility-sheet.js';
/** * Button styled via utility classes from a shared stylesheet — simulates a * Tailwind/UnoCSS workflow. All utility consumers update in place when the * generated CSS changes (e.g. a new theme color), without re-rendering. * * Approach benefits/problems: see `hmr-utility-sheet` (fetched shared sheet). */
@customElement('hmr-utility-btn')export class HmrUtilityBtn extends LitElement { static override styles = [utilitySheet];
@property() variant: 'blue' | 'red' = 'blue';
private renders = 0;
override render() { const bgClass = this.variant === 'blue' ? 'bg-blue' : 'bg-red'; return html` <button class="${bgClass} text-white font-bold py-2 px-4 rounded border-none cursor-pointer" > <slot></slot> </button> <span class="font-mono text-xs" id="badge">renders: 0</span> `; }
override updated() { this.renders++; this.setAttribute('data-renders', String(this.renders)); const badge = this.renderRoot.querySelector('#badge'); if (badge !== null) { badge.textContent = `renders: ${this.renders}`; } }}