Styling: scoped CSS, class and style

Svelte · Practical · 10 min

Styling is half of UI work, and Svelte's scoping rules decide which selectors actually apply.

Styles in a component's <style> block are scoped: Svelte adds a hashed class to the elements in this component and to your selectors. Consequences:

  • Selectors only match elements written in this component's markup, not elements rendered by child components or inserted with {@html}.
  • Unused selectors produce a compiler warning and are removed.
  • Escape scoping with `:global(...)`, e.g. .card :global(p) to style paragraphs inside children, or a :global { ... } block.

For dynamic classes, the `class` attribute accepts objects and arrays (Svelte 5.16+, using clsx rules): class={['btn', { active, disabled: !enabled }, size]}. Truthy object keys are added, falsy values are skipped. The older `class:name={cond}` directive still works, with shorthand class:active.

`style:prop={value}` sets one inline style property: style:color={color}, style:--progress="{pct}%"; add |important if needed.

To let parents theme a child without breaking scoping, use CSS custom properties. The child uses var(--accent, tomato); the parent passes <Slider --accent="purple" />, which Svelte applies on a wrapper element.

You can still use Tailwind, CSS modules or global stylesheets; scoped styles are simply the default for component-specific CSS.

Example

<script>
  let active = $state(true);
  let size = $state('lg');
  let pct = $state(40);
</script>

<button class={['btn', size, { active }]} onclick={() => (active = !active)}>
  Toggle                                 <!-- class="btn lg active" -->
</button>
<div class="bar" style:--pct="{pct}%"></div>
<div class="content">{@html '<p>from html</p>'}</div>

<style>
  .btn { padding: 0.5rem 1rem; }
  .btn.active { background: var(--accent, #d63a0f); color: white; }
  .lg { font-size: 1.25rem; }
  .bar { width: var(--pct); height: 4px; background: green; }
  .content :global(p) { margin: 0; }     /* reaches into {@html} content */
</style>

Remember

  • Component CSS is scoped; :global(...) opts out for specific selectors
  • class={[..., { name: cond }]} handles conditional classes; class: still works
  • style:prop and CSS custom properties pass dynamic values and theming

Watch out for

  • Styling a child component's elements from the parent with plain selectors; scoping prevents it, use :global or custom properties.

Check yourself

  1. A parent's `<style>` has `.card p { color: red }`, but the `<p>` is rendered inside a child component. Is it red?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: svelte.dev/docs/svelte/class