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 sameanimationblock.style="..."Keyframe names in attributes,{@html}content, JS, andstyle:colordirectivesare 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: contentsbutstillappears 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
- Scoped style isolation — implemented via hashed classes, it doesn't truly "hide" styles (visible in page source)
- Scoped selectors +0-1-0 specificity — always higher than a global selector of the same shape
@keyframesnames are also scoped —style="animation: name"can't find the scoped name (use-global-){@html}content is not affected by scoped — inserted HTML has no scope class- CSS variables don't need scoped —
var(--custom)naturally cross scopes and cascade svelte-css-wrapperelement — inserted when the parent passes--var, breaking the>child selector:globaldoesn't lower specificity — it exits the scope, but not specificity- Nested
<style>is global — doesn't go through the Svelte compiler, inserted into the DOM as-is - The compiler only rewrites within the same
<style>blockanimation— elsewhere you need-global- <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 |