Skip to content

DOM Type โ€” Light vs Shadow โ€‹

Every mono-* component ships in two flavors: a light-DOM build and a shadow-DOM build. Most of the time you don't think about it โ€” you just import a component and use it. This page explains the difference in plain terms and which one to pick.

What is light vs shadow DOM? โ€‹

Think of a component as a small box of HTML + CSS that you drop onto your page.

  • Light DOM โ€” the box is open. Its markup lives directly in the normal page, and it's styled by the page's global stylesheet (mono-helper's CSS). Your page can see and reach into it. Simple and shared.
  • Shadow DOM โ€” the box is sealed. The component keeps its own markup and styles inside a private bubble (a shadow root). Outside CSS can't leak in and its internal styles can't leak out โ€” it's self-contained and isolated.

Both render the exact same component; they only differ in where the markup/styles live and how isolated they are.

The two builds mono ships โ€‹

Light (default)Shadow
Importimport 'mono-helper/ui/button'import 'mono-helper/ui/shadow/button'
Tag you write<mono-button><mono-shadow-button>
Styles come fromthe global mono-helper CSSthe component's own scoped styles
Built forclient-side apps (SPA)server-side rendering (@lit-labs/ssr)

Theming works on both

CSS custom properties inherit through the shadow boundary, so your --mono-* overrides (see Theme and each component's CSS Variables section) apply to the light and shadow builds the same way.

Which should I use? โ€‹

The right flavor depends on whether your app renders on the server.

  • Vue app (host or remote) โ†’ use light (the default). A Vue SPA renders in the browser, so there's no server paint to optimize โ€” light DOM is the simplest, smallest path. Just import 'mono-helper/ui/<name>' and use <mono-<name>>.

  • Nuxt host (SSR) โ†’ use shadow. Because Nuxt renders on the server, the shadow build lets the server send the fully-styled component in the first HTML response (as Declarative Shadow DOM). The result: no skeleton delay, no unstyled flash, and no reflow when the page hydrates โ€” a smoother initial load and reload.

    In a Nuxt host you don't hand-write <mono-shadow-*> or wrap anything: just import 'mono-helper/ui/shadow/<name>' and the mono-helper/nuxt module auto-wraps it for SSR โ€” you keep using the component normally.

Rule of thumb: Vue โ†’ light ยท Nuxt host โ†’ shadow. A Vue remote stays light even under a Nuxt host โ€” it's still a client-rendered island; the shadow build is for the Nuxt host's own server-rendered shell and pages.

See it in the docs โ€‹

Every component's live demo on this site has a Shadow tab next to Vue and CSS. Toggle it to see the same component rendered through its shadow build โ€” it looks identical, because theming (--mono-* variables) and ::part cross the shadow boundary by design.

Quick comparison โ€‹

LightShadow
Importmono-helper/ui/<name>mono-helper/ui/shadow/<name>
Tag<mono-<name>><mono-shadow-<name>>
Style sourceglobal mono-helper CSSencapsulated per-component styles
First paint (SSR)needs client hydration firstpainted on the server, no flash
Best forVue apps (SPA)Nuxt host (SSR)
--mono-* themingโœ…โœ… (vars pierce the boundary)

New here? Start with Getting Started and Theme. If you're the AI maintaining a mono template, this choice is codified as Rule 11 ("Component flavor") in the Template Rules.