SuperAPI AI Directory

svelte-styling

Frontend & websites

View repository

Updated 10-16 07:23

About this skill

Svelte Styling Reference (Svelte 5)

This skill covers the styling system of Svelte components, including scoped styles, global styles, CSS custom properties, class handling,style: directives, nested <style> and scoped keyframes.

When to use this skill

Use this skill when the user needs to write styles for Svelte components, handle style isolation, CSS variable passing,style: directives, animation keyframes, or understand how the Svelte compiler handles styles.

Critical: Scoped Styles

<style> CSS in blocks is by defaultscoped—it only affects elements within the current component:

<style>
  p { color: burlywood; }
  .container { padding: 1em; }
</style>

At compile time, Svelte automatically adds a hash class to selectors within the scope (such as svelte-123xyz)。

Specificity

Scoped selectors add extra +0-1-0 specificity (from the scope class), which may override the same global selector:

/* 全局 */
p { color: blue; }

/* 组件内(scoped,优先级更高) */
p { color: red; }

:where() wrapping

When the same scope class needs to appear multiple times in a selector,only the first occurrenceactually contributes specificity; subsequent occurrences are wrapped in :where(.svelte-xyz123) , contributing zero specificity. This avoids a specificity explosion in deep selectors (such as .a .b .c).

Scoped Keyframes

@keyframes names are also automatically scoped, and within the same component animation: name references are rewritten to match:

<style>
  @keyframes bounce {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-20px); }
  }
  .animated { animation: bounce 1s; }  /* 编译为 bounce-svelte-abc123 */
</style>

When styles do not take effect

  • The element is rendered by a child component (scoped does not cross component boundaries)
  • {@html} content (no scope class)
  • The class name is added dynamically/programmatically, and the selector does not use :global(...)
  • The selector does not match an element that actually exists in the template
  • In style="" attributes or JS, use a scoped keyframe name (use -global- to resolve)

Critical: Global Styles

:global(...) single selector

<style>
  :global(body) { margin: 0; }          /* 全局 body */
  div :global(strong) { color: goldenrod; } /* 嵌套全局 */
  p:global(.big.red) { font-weight: bold; } /* 带条件 */
</style>
  • :global(selector) — the entire selector exits the scope.
  • parent :global(child) — the parent retains the scope, the child does not (the most commonly used form).
  • tag:global(.class.class) — the element still has the scope class, but the trigger class does not need to appear in the template (suitable for classes added at runtime by third-party libraries).

:global { ... } block (multiple selectors)

<style>
  :global {
    p { margin: 1em; }
    .theme-dark { --bg: #111; }
  }

  .parent :global {
    .foo .bar { color: red; }  /* .parent 是 scoped,.foo .bar 是全局 */
  }
</style>

:global Selectors in the block are emitted as-is,withoutreducing specificity—only .foo .bar 's own specificity.

Global Keyframes

Prefix the keyframe name with -global-:

<style>
  @keyframes -global-my-animation-name {
    0% { opacity: 0; }
    100% { opacity: 1; }
  }
</style>

After compilation, the -global- prefix is removed, becoming my-animation-name, which can be referenced by other components,<style> attributes, and JS.

The compileronlyrewrites <style> references within the same animation block.style="..." Keyframe names in attributes,{@html} content, JS, andstyle:color directivesare notrewritten—these places require -global- names.

Critical: CSS Custom Properties

Passing in custom properties

<Slider bind:value --track-color="black" --thumb-color="rgb(255 0 0)" />

At compile time, Svelte wraps a layer of svelte-css-wrapper(or <g>in SVG) to carry the inline style:

<svelte-css-wrapper style="display: contents; --track-color: black; --thumb-color: rgb(255 0 0)">
  <Slider ... />
</svelte-css-wrapper>

The wrapper element has display: contents butstillappears in > child selector matching—use a descendant selector instead when necessary.

Reading within a component

<style>
  .track { background: var(--track-color, #aaa); }
  .thumb { background: var(--thumb-color, blue); }
</style>

CSS custom properties can cascade

No need to pass values directly; as long as the parent element defines a CSS variable, the child component can read it:

/* 全局 */
:root { --primary: #ff3e00; }

Critical: style: Directive

style: The directive is shorthand for the style="..." attribute, one directive per attribute.

<div style:color="red">A</div>           <!-- 字符串字面量 -->
<div style:color={myColor}>B</div>        <!-- 表达式 -->
<div style:color>C</div>                  <!-- 简写:变量名与属性名相同 -->

Multiple styles

<div style:color style:width="12rem" style:--columns={columns}>...</div>

CSS custom properties

style:--var Set a CSS variable:

<div style:--columns={columns} style:--gap="1rem">...</div>

!important modifier

style:color|important="red" → compiles to style="color: red !important"。

Specificity

style: directivewinsover the style="" attribute on the same element—even if style="" uses !important:

<div style:color="red" style="color: blue !important">This will be red</div>

Numeric units

Numbers do not automatically get units:

<div style:width={10}>...</div>          <!-- 错误:width: 10 -->
<div style:width="{10}px">...</div>      <!-- 正确 -->

Critical: class Attribute

In Svelte 5.16+, the class attribute supports objects and arrays (handled with clsx ).

Object form

<div class={{ cool: isCool, large: isLarge }}>...</div>

Array form

<div class={[faded && 'saturate-0 opacity-50', large && 'scale-200']}>...</div>

Mixed

<Button class={['btn', props.class]} />

Type safety

<script lang="ts">
  import type { ClassValue } from 'svelte/elements';
  let { class: cls }: { class: ClassValue } = $props();
</script>
<div class={['original', cls]}>...</div>

Falsy behavior before 5.16

For historical reasons,class={false} is serialized as class="false", but class={undefined} / class={null} makes the attribute disappear. Future versions of Svelte will omit class for all falsy values.

Critical: class: Directive (no longer recommended)

Svelte 5.16+ recommends using the class={} object/array form instead of the class: directive:

<!-- 等价 -->
<div class={{ cool, lame: !cool }}></div>
<div class:cool class:lame={!cool}></div>

Critical: Nested <style> Elements

A component can only haveone top-level <style> block, but can nest <style> tags (inserted directly into the DOM without scoping):

<div>
  <style>
    /* 原生 style 标签,无作用域,规则全局生效 */
    div { color: red; }
  </style>
</div>

Use cases:

  • Theme token {#if} switching (variables are added/removed with the switch)
  • CSS bundled with third-party components
  • Dynamic theme variables
  • Email / SVG templates

Note: nested in <style> the @keyframes names areglobal, andwill notbe rewritten. Keyframes with the same name in multiple components will conflict.

Critical: {@html} and Scoped Styles

{@html} Content injected byhas noscoped class, so scoped selectors will not match. Use :global(...) to solve this:

<style>
  :global(.highlight) { color: red; }
</style>

Warning: never use :global(...) to style HTML you cannot fully control—XSS risk.

Quick Fixes

Problem Solution
Styles not taking effect Check whether the scoped hash covers it; use :global
Component styles cannot be passed in Use CSS custom properties (--var) instead of scoped selectors
Parent component cannot override child component styles Use --var to pass values, or use :global(not recommended)
Global styles overridden by component scoped styles Scoped adds +0-1-0 specificity; use :where() or increase global selector specificity
Tailwind classes not taking effect JIT cannot find dynamically concatenated class names; use class={[...]} array mapping or safelist
Dynamic style="animation: x" cannot find keyframe Keyframes are scoped; use @keyframes -global-x
style: not taking effect Check the expression syntax; numeric values need units
> .child does not match When the parent passes --var , it wraps svelte-css-wrapper; switch to descendant selectors
slot content styles don't reach use ::slotted(selector) to style from the parent component

Gotchas

  1. Scoped style isolation — implemented via hashed classes, it doesn't truly "hide" styles (visible in page source)
  2. Scoped selectors +0-1-0 specificity — always higher than a global selector of the same shape
  3. @keyframes names are also scoped — style="animation: name" can't find the scoped name (use -global-)
  4. {@html} content is not affected by scoped — inserted HTML has no scope class
  5. CSS variables don't need scoped — var(--custom) naturally cross scopes and cascade
  6. svelte-css-wrapper element — inserted when the parent passes --var , breaking the > child selector
  7. :global doesn't lower specificity — it exits the scope, but not specificity
  8. Nested <style> is global — doesn't go through the Svelte compiler, inserted into the DOM as-is
  9. The compiler only rewrites within the same <style> block animation — elsewhere you need -global-
  10. <style> attribute and scoped keyframe names in JS — not rewritten

FAQ

Q: How do I make styles affect the inside of a child component? A: Two ways: ① the parent component passes in CSS custom properties (recommended); ② use :global(affects globally, breaks encapsulation).

Q: What are the class names after Svelte compiles? A: A hash of the form svelte-123xyz , unique per component.

Q: Will scoped styles win over global styles? A: Yes. Scoped adds +0-1-0 specificity. A global p of the same shape loses to a scoped p。

Q: When to use :global instead of CSS variables? A: When you must control the selector itself (position, what is selected) rather than passing in a value, use :global. Most "pass a value" scenarios are better with CSS variables.

Q: Why don't falsy values in the class:[...] array become strings? A: Svelte filters out false/null/undefined/""/0。class={false} using clsx rules, only then does it become class="false"(historical behavior, array form changed in 5.16+).

Q: Why are rules in nested <style> global? A: Nested <style> doesn't go through the Svelte compiler's scoping, and is inserted into the DOM as-is. The browser parses it like a normal <style> .

Q: Can scoped keyframes be triggered from outside? A: No. Scoped keyframe names are rewritten at compile time, and outside code can't reference them by bare name. Use @keyframes -global-name。

Q: style: directive and style="..." attribute used together? A: style: wins, even if style="" uses !important. This is the Svelte 5 design.

Q: Can I use template strings like bg-${color}-500 for dynamic Tailwind classes? A: It won't work reliably — the JIT can't see the full class name. Use a lookup table ({ red: 'bg-red-500', ... }) or safelist。

Examples

File Description
examples/README.md Entry point listing all example files with descriptions
examples/scoped-styles.md Scoped styles, specificity, :where() trick, when styles don't apply, compiled output, svelte-css-wrapper
examples/scoped-keyframes.md @keyframes scoping, the -global- prefix, sharing animations, triggering from JS, prefers-reduced-motion
examples/global-patterns.md :global(...) single, compound, nested, block, and when to use vs CSS custom properties
examples/style-directive.md style:property, style:--var, |important, expressions, multiple styles, value coercion
examples/nested-style.md Raw <style> tags inside templates, {#if}/{#each} style fragments, lifecycle, gotchas
examples/tailwind.md Tailwind CSS, arbitrary values, dynamic theming with custom properties, safelist, twMerge

References

File Description
references/README.md Entry point listing all reference files with descriptions
references/scoped-deep.md Deep dive into scoping mechanism, hash generation, :where() wrapping, nested <style>, edge cases
references/scoped-keyframes.md Scoped keyframes, the -global- prefix, sharing across components, rewriter rules
references/global-reference.md Full reference for :global(...) single, nested, block, compound, with cascade and transform examples
references/specificity.md Specificity interactions, :where() / :is() / :not(), override strategies, cascade order
references/style-directive.md All style: forms, style:--var serialization, |important, value coercion, precedence
references/css-custom-properties.md CSS Custom Properties: passing, reading, cascading, dynamic updates
references/class-directive.md class vs class: directive, ClassValue types, clsx patterns