Skip to content

Lit Styles

Because Lit components render into Shadow DOM by default, styling works differently than global page CSS. This lesson gives an overview before the dedicated lessons on the css tag, custom properties, and parts.

Shadow DOM Encapsulates Styles by Default

Any CSS defined inside a Lit component's static styles field only applies within that component's own Shadow DOM — it cannot leak out to affect the rest of the page, and page-level stylesheets cannot reach in to affect the component's internals either. This is one of Shadow DOM's core guarantees, and it's what makes Lit components genuinely reusable without CSS naming collisions (no more .card__title--variant-2 style BEM workarounds needed purely for isolation).

This encapsulation is a deliberate trade-off: it eliminates an entire category of styling bugs (accidental overrides, specificity wars) at the cost of needing explicit mechanisms — CSS custom properties and ::part() — to intentionally allow some styling to cross that boundary when a component author wants to permit it.

import { LitElement, html, css } from 'lit';

class InfoBadge extends LitElement {
  static styles = css`
    span {
      background: #eef;
      color: #224;
      padding: 0.15rem 0.5rem;
      border-radius: 999px;
      font-size: 0.85rem;
    }
  `;

  render() {
    return html`<span><slot></slot></span>`;
  }
}

These styles apply only to the <span> inside this component's Shadow DOM — a page-level span { color: red; } rule elsewhere has no effect on it.

The static styles Field

static styles = css`
  /* your component's scoped CSS */
`;

// Or an array of multiple style sheets:
static styles = [sharedStyles, css`/* component-specific overrides */`];
  • static styles is a class field (or getter) holding the result of the css tagged template function.
  • It accepts either a single css result or an array of them, which Lit combines for the component.
  • Styles defined here apply automatically to the component's Shadow DOM — no manual <style> tag needed in the template.
  • The :host selector targets the custom element itself, from the inside — covered in the CSS tag lesson.

Styling Mechanisms Overview

Every tool available for styling a Lit component, at a glance.

Mechanism Purpose
`static styles = css`...` Scoped styles for the component's own Shadow DOM
:host Selects the custom element itself from inside its own styles
CSS custom properties Values consumers can override from outside (theming)
::part() Exposes specific internal elements for external styling
::slotted() Styles top-level projected light DOM content
Adopted stylesheets Sharing a CSSStyleSheet object across multiple components efficiently

Why Use css` Instead of a <style>` Tag in the Template?

You technically *can* put a <style> tag directly inside your render() template, but Lit's static styles mechanism is more efficient: styles declared this way are parsed once (and, where supported, shared across every instance of the component via Constructable Stylesheets) rather than being re-parsed as part of every single render pass.

Approach Parsed Shared Across Instances
`static styles = css`...` Once, at class definition Yes, via adopted stylesheets where supported
<style> inside render() Potentially on every render No — duplicated per instance

Some Things Still Cross the Boundary

Not everything is blocked by Shadow DOM: inherited CSS properties (like font-family, color, and line-height) do cascade in from ancestors unless explicitly overridden, and CSS custom properties (--my-var) also cross the boundary by design, which is exactly what makes them the standard theming mechanism for Web Components.

Common Mistakes

  • Expecting a page-level stylesheet selector to reach into a component's Shadow DOM and style its internals directly.
  • Putting all styles inline in a <style> tag inside render() instead of using the more efficient static styles field.
  • Forgetting that inherited properties like font-family and color still cascade into Shadow DOM unless the component resets them.
  • Not providing any theming hooks (CSS custom properties or parts), making a component impossible for consumers to restyle at all.

Key Takeaways

  • Shadow DOM encapsulates a Lit component's styles by default, in both directions.
  • static styles = css`...` is the idiomatic, efficient way to declare a component's scoped CSS.
  • Inherited CSS properties and custom properties are the two things that do cross the Shadow DOM boundary.
  • Deliberate theming mechanisms (custom properties, ::part()) exist for when you want to allow external styling.

Pro Tip

Before writing a single style rule, sketch out which parts of your component's appearance should be fixed (encapsulated, protected from page CSS) versus themeable (exposed via custom properties or parts) — deciding this upfront leads to a much cleaner styling API than retrofitting theming hooks later.