# Technical Documentation: carbon-design-system/carbon-components-svelte

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/carbon-design-system/carbon-components-svelte)

### Overview (`README.md`)

# carbon-components-svelte

[![NPM][npm]][npm-url]

<a href="https://discord.gg/J7JEUEkTRX">
<img src="https://img.shields.io/discord/689212587170201628?color=5865F2&style=for-the-badge" alt="Chat with us on Discord">
</a>

Carbon Components Svelte is a [Svelte](https://github.com/sveltejs/svelte) component library that implements the [Carbon Design System](https://www.carbondesignsystem.com/), an open source design system by IBM. Ship accessible, consistent, production-ready interfaces.

- **100+ components**: from inputs to data tables
- **5 built-in themes**: two light, three dark
- **Fully typed TypeScript API**: props, events, and slots
- **WCAG 2.1 AA**: keyboard and screen-reader ready

The Carbon Svelte ecosystem also includes:

- **[Carbon Icons Svelte](https://github.com/carbon-design-system/carbon-icons-svelte)**: 2,700+ Carbon icons as Svelte components
- **[Carbon Pictograms Svelte](https://github.com/carbon-design-system/carbon-pictograms-svelte)**: 1,500+ Carbon pictograms as Svelte components
- **[Carbon Charts Svelte](https://github.com/carbon-design-system/carbon-charts/tree/master/packages/svelte)**: 25+ charts, powered by d3
- **[Carbon Preprocess Svelte](https://github.com/carbon-design-system/carbon-preprocess-svelte)**: Svelte preprocessors and build plugins for Carbon

## [Documentation](https://svelte.carbondesignsystem.com)

## Installation

```sh
# npm
npm i carbon-components-svelte

# pnpm
pnpm add carbon-components-svelte

# Yarn
yarn add carbon-components-svelte

# Bun
bun add carbon-components-svelte
```

## Usage

### Styling

Before importing components, you will need to first apply Carbon component styles. The Carbon Design System supports five themes (2 light, 3 dark).

- **white.css**: Default Carbon theme (light)
- **g10.css**: Gray 10 theme (light)
- **g80.css**: Gray 80 theme (dark)
- **g90.css**: Gray 90 theme (dark)
- **g100.css**: Gray 100 theme (dark)
- **all.css**: All five themes (White, Gray 10, Gray 80, Gray 90, Gray 100) using [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties)

Each StyleSheet is [compiled](scripts/build-css.ts) from Carbon v10 SCSS ([carbon-components@10.58](css/vendor/carbon-components), vendored) and this library's own partials in [css](css).

The compiled CSS is generated from the following `.scss` files:

- [css/white.scss](css/white.scss)
- [css/g10.scss](css/g10.scss)
- [css/g80.scss](css/g80.scss)
- [css/g90.scss](css/g90.scss)
- [css/g100.scss](css/g100.scss)
- [css/all.scss](css/all.scss)

Import one theme once at the top-level entry point of your app, like `index.js` or `src/+layout.svelte`.

#### CSS StyleSheet

```js
// White theme
import "carbon-components-svelte/css/white.css";

// Gray 10 theme
import "carbon-components-svelte/css/g10.css";

// Gray 80 theme
import "carbon-components-svelte/css/g80.css";

// Gray 90 theme
import "carbon-components-svelte/css/g90.css";

// Gray 100 theme
import "carbon-components-svelte/css/g100.css";

// All themes
import "carbon-components-svelte/css/all.css";
```

### Smaller CSS

Each precompiled StyleSheet contains the styles for every component. To ship only the styles your app uses, add [`optimizeCss`](#optimizecss) to your production build. Set `propAware: true` to also drop the styles of prop values your app never passes; see [prop-aware pruning](#prop-aware-pruning).

The SCSS sources are not published. Compiling `carbon-components` v10 SCSS yourself misses styles for components and variants that only this library provides.

### Dynamic theming

To switch themes at runtime, import the combined "all.css" StyleSheet instead of a single theme. It bundles all five themes and toggles between them through a `theme` attribute on the HTML element.

```js
import "carbon-components-svelte/css/all.css";
```

Update the theme by setting the `theme` attribute on the `html` element. The default `theme` is `"white"`.

```html
<!doctype html>
<html lang="en" theme="g10">
  <body>
    ...
  </body>
</html>
```

Or programmatically switch between each of the five Carbon themes by setting the `theme` attribute on the HTML element.

```html
<script>
  let theme = "white"; // "white" | "g10" | "g80" | "g90" | "g100"

  $: document.documentElement.setAttribute("theme", theme);
</script>
```

Alternatively, use the [Theme component](https://svelte.carbondesignsystem.com/components/Theme) to manage the theme reactively.

### Importing components

Import components from `carbon-components-svelte` in the `script` tag of your Svelte file. Visit the [documentation site](https://svelte.carbondesignsystem.com) for examples.

```html

<script>
  import { Accordion, AccordionItem } from "carbon-components-svelte";
</script>

<Accordion>
  <AccordionItem title="Section 1" open> Content 1 </AccordionItem>
  <AccordionItem title="Section 2"> Content 2 </AccordionItem>
  <AccordionItem title="Section 3"> Content 3 </AccordionItem>
</Accordion>
```

**Refer to the [documentation site](https://svelte.carbondesignsystem.com) for component API documentation.**

## Preprocessors & Plugins

[carbon-preprocess-svelte](https://github.com/carbon-design-system/carbon-preprocess-svelte) is a collection of Svelte preprocessors and build plugins for Carbon. It trims build times and bundle size with three drop-in tools: faster compiles in development, leaner CSS and leaner JavaScript when you ship. Each works on its own.

> [!NOTE]
> Using `carbon-preprocess-svelte` is optional and not a prerequisite for this library. It should be installed as a development dependency.

```sh
# npm
npm i -D carbon-preprocess-svelte

# pnpm
pnpm add -D carbon-preprocess-svelte

# Yarn
yarn add -D carbon-preprocess-svelte

# Bun
bun add -D carbon-preprocess-svelte
```

| Tool | Type | What it does |
| :--- | :--- | :--- |
| [`optimizeImports`](#optimizeimports) | Svelte preprocessor | Rewrites barrel imports to source paths, for faster dev and build times |
| [`optimizeCss`](#optimizecss) | Build plugin | Removes unused Carbon styles from production CSS |
| [`optimizeComponents`](#optimizecomponents) | Build plugin | Rewrites Carbon components for the props your app passes, removing code it never runs |

### `optimizeImports`

`optimizeImports` is a Svelte preprocessor that rewrites barrel imports from Carbon components/icons/pictograms packages to their source Svelte code paths. This can significantly speed up development and production build compile times while preserving typeahead and autocompletion offered by integrated development environments (IDE) like VS Code.

The preprocessor optimizes imports from the following packages:

- [carbon-components-svelte](https://github.com/carbon-design-system/carbon-components-svelte)
- [carbon-icons-svelte](https://github.com/carbon-design-system/carbon-icons-svelte)
- [carbon-pictograms-svelte](https://github.com/carbon-design-system/carbon-pictograms-svelte)

**Before & After**

```diff
- import { Button } from "carbon-components-svelte";
+ import Button from "carbon-components-svelte/src/Button/Button.svelte";

- import { Add } from "carbon-icons-svelte";
+ import Add from "carbon-icons-svelte/lib/Add.svelte";

- import { Airplane } from "carbon-pictograms-svelte";
+ import Airplane from "carbon-pictograms-svelte/lib/Airplane.svelte";
```

### `optimizeCss`

`optimizeCss` is a build plugin that removes unused Carbon styles at build time, often removing hundreds of kilobytes from production bundles. It runs on Vite, Rollup, and Rolldown. For Webpack and Rspack, use `OptimizeCssPlugin`.

`carbon-components-svelte@0.85.0` or greater is required.

```diff
$ vite build

Optimized assets/index-Doxsm5Yz.css
- Before: 557.37 kB
+ After:   46.16 kB (-91.72%)

dist/index.html                  0.33 kB │ gzip:  0.23 kB
dist/assets/index-Doxsm5Yz.css  46.16 kB │ gzip:  6.12 kB
dist/assets/index-BoBcXTHi.js   69.61 kB │ gzip: 24.35 kB
```

> [!NOTE]
> This is a plugin and not a Svelte preprocessor. Add it to your bundler's `plugins`. Vite runs it on `vite build` only, and `OptimizeCssPlugin` runs in Webpack's production mode only. Under Rollup and Rolldown, add it to production builds only.

#### Prop-aware pruning

Set `propAware: true` to also remove styles for the prop values your app never passes.

```js
optimizeCss({ propAware: true });
```

```svelte
<Button kind="tertiary">Save</Button>

```

It reads literal props, constants and state nothing reassigns (including constants your own modules export), your own wrapper components, props forwarded with `{...$$restProps}` or `...rest`, object and array literals, and your own `{#if}` and `{#each}`. A value it can't read keeps every variant: a reassigned variable, a writable store, `bind:`, a spread of anything but a literal or forwarded props, or data fetched at runtime.

Refer to the [carbon-preprocess-svelte documentation](https://github.com/carbon-design-system/carbon-preprocess-svelte#prop-aware-pruning) for details.

### `optimizeComponents`

`optimizeComponents` rewrites each Carbon component your app renders for the props it passes. Values that never change become literals, and branches that can't run are removed, along with child components only those branches render (a skeleton, a tooltip portal). It is the JavaScript counterpart of prop-aware CSS pruning and uses the same analysis. In typical apps it removes 18–27% of the production JavaScript.

Your own wrapper components are rewritten the same way when every place that renders them is in `content`.

```js
// vite.config.js
import { svelte } from "@sveltejs/vite-plugin-svelte";
import {
  optimizeComponents,
  optimizeCss,
  optimizeImports,
} from "carbon-preprocess-svelte";

export default {
  plugins: [
    optimizeComponents(),
    svelte({ preprocess: [optimizeImports()] }),
    optimizeCss({ propAware: true }),
  ],
};
```

| Bundler | Set-up | Runs on |
| :--- | :--- | :--- |
| Vite, SvelteKit, Astro | add `optimizeComponents()` to `vite.plugins`; it orders itself first | `vite build` only, never `vite dev` |
| Rollup, Rolldown | list `optimizeComponents()` before the Svelte plugin | production builds only (add it conditionally) |
| Webpack, Rspack | add `new OptimizeComponentsPlugin()` to `plugins` | `mode: "production"` only |

- It analyzes the files matched by `content` (default: source files under `src/`) before the build. If a module outside `content` renders a Carbon component, or one of your rewritten wrappers, including through a re-export or a dynamic `import()`, the build fails instead of shipping wrong code.
- Wrappers imported through an alias (`$lib/Card.svelte`, `#lib/Card.svelte`) are resolved with the bundler's own resolver.
- SvelteKit and Astro server and client builds are rewritten alike, so prerendered pages hydrate as before.
- Set `report: true` to print what each build rewrote.

Refer to the [carbon-preprocess-svelte documentation](https://github.com/carbon-design-system/carbon-preprocess-svelte#optimizecomponents) for every option.

### Configure your bundler

Add `optimizeImports` to your Svelte preprocessor and `optimizeCss` to your bundler plugins. See [examples](examples) for full configurations.

**Vite**

```js
// vite.config.js
import { svelte, vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import { optimizeCss, optimizeImports } from "carbon-preprocess-svelte";

export default {
  plugins: [
    svelte({
      preprocess: [vitePreprocess(), optimizeImports()],
    }),
    optimizeCss(),
  ],
};
```

**SvelteKit**

```js
// vite.config.js
import adapter from "@sveltejs/adapter-static";
import { sveltekit } from "@sveltejs/kit/vite";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import { optimizeCss, optimizeImports } from "carbon-preprocess-svelte";

export default {
  plugins: [
    sveltekit({
      preprocess: [vitePreprocess(), optimizeImports()],
      adapter: adapter(),
    }),
    optimizeCss(),
  ],
};
```

On SvelteKit 2, pass the same `preprocess` in `svelte.config.js` and the adapter under `kit`.

**Rollup**

```js
// rollup.config.js
import svelte from "rollup-plugin-svelte";
import { optimizeCss, optimizeImports } from "carbon-preprocess-svelte";

const production = !process.env.ROLLUP_WATCH;

export default {
  plugins: [
    svelte({
      preprocess: [optimizeImports()],
    }),
    production && optimizeCss(),
  ],
};
```

**Webpack**

Rspack implements Webpack's plugin and loader APIs, so the same configuration works in `rspack.config.mjs`.

```js
// webpack.config.mjs
import { OptimizeCssPlugin, optimizeImports } from "carbon-preprocess-svelte";

export default {
  module: {
    rules: [
      {
        test: /\.svelte$/,
        use: {
          loader: "svelte-loader",
          options: {
            preprocess: [optimizeImports()],
          },
        },
      },
    ],
  },
  plugins: [new OptimizeCssPlugin()],
};
```

## Icons and pictograms

The icon and pictogram sets ship as separate packages of individual Svelte components. Each is optional, so install only what you need.

### Icons

2,700+ icons designed for product UI, in four sizes (16, 20, 24, and 32 pixels), set with a single prop.

```sh
# npm
npm i carbon-icons-svelte

# pnpm
pnpm add carbon-icons-svelte

# Yarn
yarn add carbon-icons-svelte

# Bun
bun add carbon-icons-svelte
```

```svelte
<script>
  import Add from "carbon-icons-svelte/lib/Add.svelte";
</script>

<Add size={24} />
```

### Pictograms

1,500+ illustrative pictograms for empty states, onboarding, hero sections, and feature callouts. Larger than icons, 64px by default.

```sh
# npm
npm i carbon-pictograms-svelte

# pnpm
pnpm add carbon-pictograms-svelte

# Yarn
yarn add carbon-pictograms-svelte

# Bun
bun add carbon-pictograms-svelte
```

```svelte
<script>
  import Cloud from "carbon-pictograms-svelte/lib/Cloud.svelte";
</script>

<Cloud />
```

## Documentation for LLMs

Documentation is available in LLM-friendly plain text for use with coding assistants, plus a standalone Markdown document for every component. Append `.md` to any component's URL to read it.

- **[llms.txt](https://svelte.carbondesignsystem.com/llms.txt)**: a component index where each entry links to its per-component Markdown doc, sized for model context windows.
- **[llms-full.txt](https://svelte.carbondesignsystem.com/llms-full.txt)**: the full component documentation in a single plain-text file.

## Examples

- [examples/rollup](examples/rollup/)
- [examples/sveltekit](examples/sveltekit/)
- [examples/vite](examples/vite/)
- [examples/webpack](examples/webpack/)

## TypeScript support

Every component ships TypeScript definitions for its props, events, and slots (`src/**/*.svelte.d.ts`), generated from the component source by [sveld](https://github.com/carbon-design-system/sveld).

## Contributing

Refer to the [Contributing guidelines](CONTRIBUTING.md).

## [Changelog](CHANGELOG.md)

## License

[Apache 2.0](LICENSE)

[npm]: https://img.shields.io/npm/v/carbon-components-svelte.svg?color=262626&style=for-the-badge
[npm-url]: https://npmjs.com/package/carbon-components-svelte

## <picture><source height="20" width="20" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ibm-telemetry/telemetry-js/main/docs/images/ibm-telemetry-dark.svg"><source height="20" width="20" media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/ibm-telemetry/telemetry-js/main/docs/images/ibm-telemetry-light.svg"><img height="20" width="20" alt="IBM Telemetry" src="https://raw.githubusercontent.com/ibm-telemetry/telemetry-js/main/docs/images/ibm-telemetry-light.svg"></picture> IBM Telemetry

This package uses IBM Telemetry to collect de-identified and anonymized metrics data in CI environments. By installing
this package as a dependency you are agreeing to telemetry collection. To opt out, see
[Opting out of IBM Telemetry data collection](https://github.com/ibm-telemetry/telemetry-js/tree/main#opting-out-of-ibm-telemetry-data-collection).
For more information on the data being collected, please see the
[IBM Telemetry documentation](https://github.com/ibm-telemetry/telemetry-js/tree/main#ibm-telemetry-collection-basics).

---

### Overview (`thumbnails/README.md`)

# Thumbnails

Every component with a docs page gets a 320×180 SVG here, named after
the page in kebab case (`ToggleButtonGroup.svx` →
`toggle-button-group.svg`). `carbon.yml` points at these through
`thumbnailPath`, and they're the preview a catalog shows next to the
component name.

New components usually ship without one, and without the `carbon.yml`
entry that makes the catalog show it. Check both before each release.

## Find what's missing

```sh
bun run thumbnails missing
```

It keys off the docs pages, not `src/`. Pages like `ClickableTile` live
inside another folder (`src/Tile/`), and folders like `ListBox` or
`FormItem` have no page. It also lists thumbnails with no page, except
`tabs-vertical.svg`, which backs the `tabs-vertical` catalog entry
(TabsVertical is documented on the Tabs page). Any other thumbnail with
no page is for a component that doesn't exist; delete it.

`missing` doesn't read `carbon.yml`. `catalog` does, and exits non-zero
on any gap between it, the docs pages, and `thumbnails/`:

```sh
bun run thumbnails catalog
```

It lists docs pages with no asset, assets whose `thumbnailPath` file is
missing, thumbnails no asset uses, and `name`s that aren't sentence case
or don't spell the asset's key or docs page (it would have caught
"Tootlip icon"). Assets without a `name` inherit it from `carbon-styles`
and skip the name checks. A page deliberately left out of the catalog
goes in `UNCATALOGED_PAGES` in `scripts/lib/thumbnails.ts`, with a line
here saying why. None are left out today.

`scripts/thumbnails.ts` has three more commands, used in the
[recipe](#recipe): `audit` checks the mechanical rules below, `sheet`
renders a contact sheet, and `themes` renders the files in all five
themes.

## Design principles

A thumbnail is a glyph, not a screenshot. People see it at a fraction
of its size in a grid full of others, so it has one job: make the
component recognizable and tell it apart from its neighbors.

**One idea per thumbnail.** Pick the single trait that defines the
component and draw only that. Meter is a track with a fill in a status
color. It isn't three meters in three states with threshold ticks. If
you're listing features, cut. The first draft is almost always too
busy.

**Abstract by default.** Text is a flat bar, not words. Media is a
filled block. Icons appear only when they carry the meaning (bold,
italic, and underline in ToggleButtonGroup, chevrons on Carousel
controls).

**Real text only when the text is the component.** Link, Text,
RelativeTime, and Truncate show literal words, because "2 days ago"
says more than any shape could. Keep it to a short phrase. A list is
not its text: OrderedList is bars with numbers, not "Ordered List"
four times.

**Distinguish from look-alikes.** Before drawing, find the existing
thumbnail it could be mistaken for and pick the trait that separates
them. Meter and ProgressBar are both a labeled track. Meter's warning
yellow fill is the difference, and ProgressBar keeps the blue. The
likeliest look-alikes sit in the same docs category, so render that
category's sheet (`bun run thumbnails sheet --category Layout`).

**Show invisible structure with dashed outlines.** Some components are
about something you can't see: rows that aren't rendered
(VirtualList), padding that content breaks out of (Bleed). A dashed
`text-disabled` outline marks a boundary or a thing that isn't there.
Solid shapes are things that render.

**Nothing cut off, no chrome.** Draw every element whole. Leave out
scrollbars, window frames, and partial rows, even when the real
component has them. A clipped shape reads as a rendering bug at
thumbnail size.

**Spend color on the point.** Stay in greys and use one accent, either
`background-brand` blue or a status color, on the element that carries
the idea: the selected tab, the pressed button, the meter fill. Two
accents compete.

**Shadows mean "floats above".** Only layers that float get a shadow:
menus, popovers, tooltips, modals, toasts. Draw it with the
[elevation recipe](#elevation) so every floating layer casts the same
one.

**Match the family.** Separator rules are 1px `border-subtle`, the
same as Divider and DescriptionList. Text bars are 6px or 8px tall.
Keep stroke weights, bar heights, and spacing consistent with the
existing set so the grid reads as one system. Reuse the
[visual vocabulary](#visual-vocabulary) for states instead of
inventing a new cue.

**Draw symbols at the weight of the bars.** The set is flat fills.
A symbol drawn as an outlined illustration, or at twice the size of
the bars beside it, looks pasted in. The first LocalStorage draft had
a 48px stroked database cylinder and read as clip art; outlined
braces at the same weight as the key/value bars fit. Strokes are for
1px borders and dashed boundaries, not for drawing objects.

**Fill the safe area.** A lone 16px icon in the middle of the canvas
disappears in the grid. A thumbnail that is one control (CopyButton,
BadgeIndicator, Loading) scales it up; see the sizes under
[Canvas and geometry](#canvas-and-geometry).

**Twins share one design.** Components that come in a pair or family
look alike on purpose and differ in one place. LocalStorage and
SessionStorage are the same braces, with the session one inside a
browser tab. The skeleton family shares one shimmer. The tooltip
family shares one bubble and differs by trigger.

**No cursors.** A pointer or hand says "interactive" about everything,
so it says nothing. Show the component's own cue instead: an arrow
icon on ClickableTile, a chevron on ExpandableTile. The exception is
ContextMenu, whose idea is a menu that opens at the pointer.

**When a symbol doesn't land, change direction.** Don't nudge the
size or color of a symbol that reads wrong. Draft three or four
different directions in `.context/`, render them side by side with
`sheet`, and pick one. Draft twins as pairs so you judge the
difference between them too.

**Break a rule only when breaking it is the idea.** ScrollGradient is
about content that runs past the edge, so its rows are cut off.
TagSet is about color variety, so it uses several. If the exception
isn't the component's one idea, the rule holds.

## Canvas and geometry

| Rule | Value |
| :--- | :--- |
| Canvas | `width="320px" height="180px" viewBox="0 0 320 180"` |
| Safe area | roughly x 64–256, y 28–152 |
| Default content width | 168 (x 76–244), centered |
| Text bars | height 8 for labels and values, 6 for body text |
| Fields | 32 tall, `field-01` with a 1px `border-strong` bottom edge; label bar 8 above, 16 to the field |
| Menus and lists | rows 18–22 apart; a highlighted row is a 22-tall `border-subtle` band |
| Buttons | 24 tall when secondary to the idea, 32 when they are the idea |
| Icons | 16px (`scale(0.5)`) inline; 20px for standalone arrows; 24–28px inside a lone control |
| Lone control | about 56×56, centered (CopyButton, BadgeIndicator) |
| Rings and spinners | radius 28, stroke 4 |
| Rules | `height="1"` rect, not a stroked line |
| 1px strokes | offset by 0.5 (`x="76.5"`) so edges stay crisp |
| Coordinates | whole numbers, except the 0.5 stroke offset |

Center the composition both ways. Measure the bounding box of
everything you drew, not just the main shape.

## Visual vocabulary

The same state should look the same in every thumbnail.

| Meaning | Draw it as | Seen in |
| :--- | :--- | :--- |
| Selected | 1px `icon-primary` outline on the tile, plus a filled control | RadioTile, SelectableTile |
| Pressed | `border-subtle` fill with a 2px `background-brand` bottom bar | ToggleButtonGroup |
| Current or active | 2px `background-brand` underline or fill | Tabs, PaginationNav, PageHeader |
| Typing | 1×14 `icon-primary` caret after the text bar | ComboBox |
| Filter match | the start of each row's bar dark, the rest light | ComboBox |
| Open | floating panel under the field, chevron pointing up | Dropdown, MultiSelect |
| Not rendered, or a boundary | 1px dashed `text-disabled` outline | VirtualList, Bleed, Breakpoint, FloatingPortal |
| Composed of parts | 1px dashed `background-brand` outline around each part | ComposedModal |
| Loading | shimmer gradient from `text-disabled` to `border-subtle` | Skeleton family |
| Stored data | outlined `{` `}` around key/value bars | LocalStorage, SessionStorage |
| Status | support color on the icon and a 3px left edge | InlineNotification, ToastNotification |
| Focus | don't draw it | |

Look-alike pairs and the trait that separates them:

| Pair | Separating trait |
| :--- | :--- |
| ComboBox / Dropdown | typing and filtered matches / a checked selection |
| ComboButton / MenuButton | a split action and trigger / one button with a menu |
| FloatingPortal / Popover | escapes a clipped container / caret on a trigger |
| RadioTile / SelectableTile | stacked radios, one selected / a row of checkboxes, several checked |
| ClickableTile / ExpandableTile / Tile | arrow / revealed section and chevron / neither |
| Modal / ComposedModal | plain / dashed outlines around header, body, and footer |
| Form / FluidForm | labels above fields / labels inside joined fields |
| Tooltip / TooltipIcon / TooltipDefinition / Toggletip | icon with a bubble above / icon button with a side label / underlined term / pressed trigger with a link inside |
| SkeletonText / SkeletonPlaceholder / SkeletonIcon | paragraph / block / small square |
| Meter / ProgressBar | status-colored fill / blue fill |

## Color tokens

Every fill and stroke uses `var(--cds-<token>, <hex>)` so the
thumbnail follows the host theme. The hex is the white-theme value
and is what renders anywhere the variable isn't defined, including
GitHub and `rsvg-convert`. Reuse these pairs exactly:

| Token | Hex | Use for |
| :--- | :--- | :--- |
| `--cds-layer-02` | `#ffffff` | Raised surfaces, containers |
| `--cds-field-01` | `#f4f4f4` | Fields, tiles, unpressed buttons |
| `--cds-border-subtle` | `#e0e0e0` | Rules, tracks, pressed or selected fills, media blocks |
| `--cds-text-disabled` | `#c6c6c6` | Secondary text bars, dashed outlines |
| `--cds-layer-accent-active` | `#a8a8a8` | Primary text bars |
| `--cds-border-strong` | `#8d8d8d` | Strong borders, emphasized blocks |
| `--cds-text-secondary` | `#525252` | Headings, terms, labels with weight |
| `--cds-button-secondary` | `#393939` | Secondary buttons, dark selected segments |
| `--cds-icon-primary` | `#161616` | Icons, real text |
| `--cds-icon-inverse` | `#ffffff` | Marks on `icon-primary` or inverse fills (checkbox checks, notification titles); flips with them |
| `--cds-text-on-color` | `#ffffff` | Labels and icons on blue, status, or secondary-button fills; white in every theme |
| `--cds-background-brand` | `#0f62fe` | The one accent: selected, active, links |
| `--cds-support-success` | `#198038` | Success status |
| `--cds-support-warning` | `#f1c21b` | Warning status |
| `--cds-support-error` | `#da1e28` | Error status |
| `--cds-background-inverse` | `#393939` | Tooltips, notifications, inverse surfaces |
| `--cds-text-inverse` | `#ffffff` | Text bars on inverse surfaces |

The thumbnails theme only when they're inlined into a page that
defines the tokens. Loaded through `<img>` or on GitHub, an SVG is its
own document, so every `var()` falls back to the hex.

Pick tokens by what they sit on, because some flip in dark themes and
some don't. `icon-inverse`, `layer-02`, and `background-inverse` flip;
`text-on-color` stays white. A label on a blue button is `text-on-color`: with
`icon-inverse` it turns dark on g80, g90, and g100. A check on an
`icon-primary` checkbox is `icon-inverse`, so the two flip together.

`bun run thumbnails audit` fails on any other token or a mismatched
hex. Raw colors fail too, except in thumbnails whose idea is literal
color (tag, tag-set, theme, user-avatar-group; see
`RAW_COLOR_EXCEPTIONS` in `scripts/lib/thumbnails.ts`).

To match what the component really renders, read its partial in
`css/_<component>.scss` and map the Sass token to the closest row
above. For example, `_meter.scss` fills the warning state with
`$support-warning`, which is `--cds-support-warning`.

## File template

```svg
<?xml version="1.0" encoding="UTF-8"?>
<svg width="320px" height="180px" viewBox="0 0 320 180" version="1.1" xmlns="http://www.w3.org/2000/svg">
    <title>component-name</title>
    <g id="component-name" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <rect id="label" fill="var(--cds-text-disabled, #c6c6c6)" x="76" y="74" width="60" height="8"></rect>
    </g>
</svg>
```

Give each part an `id` that names what it is (`track`, `fill`,
`selected`). The ids make the file reviewable without rendering it.

Draw plain shapes. No `<use>`, `<image>`, or `xlink:href`: they're
how design-tool exports reuse geometry, and they make the file harder
to read and edit. Keep each file under 8 KB; a bigger one is usually
outlined text or exported cruft.

## Elevation

A floating layer is two rects with the same geometry: a black one
under the shadow filter, then the surface on top.

```svg
<defs>
    <filter id="component-name-shadow" filterUnits="userSpaceOnUse" x="0" y="0" width="320" height="180">
        <feOffset dy="2" in="SourceAlpha" result="offset"></feOffset>
        <feGaussianBlur stdDeviation="3" in="offset" result="blur"></feGaussianBlur>
        <feColorMatrix in="blur" type="matrix" values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.3 0"></feColorMatrix>
    </filter>
</defs>
<rect id="shadow" fill="black" filter="url(#component-name-shadow)" x="100" y="48" width="120" height="44"></rect>
<rect id="surface" fill="var(--cds-layer-02, #ffffff)" x="100" y="48" width="120" height="44"></rect>
```

The filter region covers the whole canvas on purpose. A region in
percentages of a short shape is too small for the blur and clips it
into a smear, which is what broke `tooltip-icon.svg`. The `fill="black"`
under a filter is the one raw color `audit` allows.

## Recipe

1. Run `bun run thumbnails missing` to list missing thumbnails.
2. For each one, read the page's frontmatter `description:` and its
   `## Basic` example. Write down the one trait you'll draw before
   opening an editor.
3. Render the sheet for its docs category
   (`bun run thumbnails sheet --category <label>`, labels from
   `docs/src/component-categories.ts`). Note what the neighbors look
   like so the new one doesn't.
4. Copy the closest existing thumbnail as a starting point. The shared
   layout, tokens, and sizes come along for free.
5. Draw it. Check colors against the component's SCSS partial. Keep
   drafts and alternatives in `.context/drafts/`, not `thumbnails/`.
   `sheet` takes paths too:
   `bun run thumbnails sheet meter .context/drafts/meter-b.svg`.
6. Check the mechanics: `bun run thumbnails audit <name>…` and
   `xmllint --noout thumbnails/<name>.svg`.
7. Check themes: `bun run thumbnails themes <name>…` renders the
   files in white, g10, g80, g90, and g100 to
   `.context/thumbnails-themes.png`. It needs a built `css/all.css`
   (`bun run build:css`). Look for marks that vanish or invert against
   their fill.
8. Review: `bun run thumbnails sheet <name>…` writes
   `.context/thumbnails-sheet.png`. Seeing the new ones side by side
   catches inconsistent weight and spacing that a single render hides.
   Check each against the principles above, and cut whatever isn't
   the one idea.
9. Commit as `chore: add thumbnail SVGs for <components>`.
10. Add a `carbon.yml` entry for each new thumbnail, in a separate
    commit (`chore: add <components> to carbon.yml`). See
    [Catalog entries](#catalog-entries).

For a cleanup that shouldn't change the picture (renaming ids,
inlining `<use>`, swapping a raw hex for its token), render every
touched file before and after and compare the pixels. Visual review
misses one-pixel shifts and dropped shadows; a diff doesn't.

## Catalog entries

`carbon.yml` lists one asset per component under `assets:`, keyed by the
thumbnail name and kept in alphabetical order. A component that Carbon
also ships inherits its name and description from `carbon-styles`, so its
entry only adds `thumbnailPath`, `externalDocsUrl`, and the demo link. A
Svelte-only component spells everything out:

```yaml
  table-of-contents:
    name: Table of contents
    status: stable
    type: component
    platform: web
    framework: svelte
    thumbnailPath: './thumbnails/table-of-contents.svg'
    demoLinks:
      - type: other
        name: Documentation
        action: link
        url: https://svelte.carbondesignsystem.com/components/TableOfContents
```

`name` is the page name in Carbon's sentence case: "Toggle button group",
not "Toggle Button Group". Acronyms stay upper case ("UI shell header",
"PIN code input"), and Carbon's one-word names stay one word
("Multiselect", "Toggletip"). A component documented inside
another page links to that page's section, as `tabs-vertical` links to
`Tabs#vertical`. Check it with `bun run thumbnails catalog`, which also fails if the file
doesn't parse.

## Gotchas

| Gotcha | Why it matters |
| :--- | :--- |
| Prefix every id that's defined in `<defs>` or referenced through `url(#…)` or `href` with the component name (`scroll-gradient-clip`) | A page that inlines several thumbnails shares one id namespace, so a bare `clip` or `shadow` id makes one SVG pick up another's definition. `sheet` rasterizes each file first for the same reason. |
| Draw floating layers without a `transform` | A `userSpaceOnUse` filter region is measured in the element's own coordinates. On a rotated or translated element the canvas-sized region moves with it and can clip the shadow. `tooltip-icon.svg` had a rotated bubble path for this reason; it's now a plain rect and a caret polygon. |
| Don't keep drafts in `thumbnails/` | A cleanup glob like `rm thumbnails/v*-*.svg` matches `virtual-list.svg` too. Drafts go in `.context/drafts/`. |
| The root `<g>` sets `stroke="none"` | Outlined shapes need an explicit `stroke` and `stroke-width`, or they render with no border. |
| Real text: outline it, don't use `<text>` | `<text>` falls back to Helvetica or Arial wherever Plex isn't installed. Link, Text, Truncate, RelativeTime, and the numerals in OrderedList and PaginationNav are outlined paths instead: IBM Plex Sans Regular (from `@ibm/plex-sans`, drawn with Python `fontTools`), 12px by default, baseline at y 95 and centered on x 160 for a single phrase. Round coordinates to one or two decimals and keep phrases short; outlined glyphs cost about 500 B each against the 8 KB budget. |
| Icons are 32-unit paths | Copy the `<path d>` from `node_modules/carbon-icons-svelte/lib/<Icon>.svelte` and apply `transform="translate(x, y) scale(0.5)"` for a 16px icon. |
| `sheet` needs `rsvg-convert` | `brew install librsvg`. No ImageMagick needed. |
| A new thumbnail doesn't add a catalog entry | The catalog only shows what `carbon.yml` lists, so a thumbnail with no entry never appears. Add the entry in its own commit; see [Catalog entries](#catalog-entries). |

---

### Content Overview (`docs/content/overview.md`)

A complete [Svelte](https://github.com/sveltejs/svelte) component library that implements the [IBM Carbon Design System](https://www.carbondesignsystem.com/). Ship accessible, consistent, production-ready interfaces.

- **100+ components** — from inputs to data tables
- **5 built-in themes** — two light, three dark
- **Fully typed TypeScript API** — props, events, and slots
- **WCAG 2.1 AA** — keyboard and screen-reader ready

## Quick start

Install the library, pick a theme, and render your first component. Three steps to a running app, plus customization options below.

### 1. Install the package

```sh
# npm
npm i carbon-components-svelte

# pnpm
pnpm add carbon-components-svelte

# Yarn
yarn add carbon-components-svelte

# Bun
bun add carbon-components-svelte
```

### 2. Apply a theme stylesheet

Import one precompiled Carbon theme. The Carbon Design System supports five themes (2 light, 3 dark): White, Gray 10, Gray 80, Gray 90, Gray 100.

Import this once at the top-level, like `index.js` or `src/+layout.svelte`.

```javascript
// White theme
import "carbon-components-svelte/css/white.css";

// Gray 10 theme
import "carbon-components-svelte/css/g10.css";

// Gray 80 theme
import "carbon-components-svelte/css/g80.css";

// Gray 90 theme
import "carbon-components-svelte/css/g90.css";

// Gray 100 theme
import "carbon-components-svelte/css/g100.css";

// All themes (for dynamic theming)
import "carbon-components-svelte/css/all.css";
```

### 3. Import a component

```svelte
<script>
  import { Button } from "carbon-components-svelte";
</script>

<Button>Hello Carbon</Button>
```

Explore the full [component index](/component-index).

## Dynamic theming

To switch themes at runtime, import the combined `all.css` stylesheet instead of a single theme. It bundles all five themes and toggles between them through a `theme` attribute on the HTML element.

Import the stylesheet once, at the top-level entry point of your app:

```javascript
import "carbon-components-svelte/css/all.css";
```

Then set the theme reactively in Svelte:

```svelte
<script>
  let theme = "white"; // "white" | "g10" | "g80" | "g90" | "g100"

  $: document.documentElement.setAttribute("theme", theme);
</script>
```

Or statically in your HTML:

```html
<html theme="white"></html>
```

Or use the [Theme component](/components/Theme) to manage the theme reactively.

## Faster builds, smaller bundles

The fast path is enough to build. [carbon-preprocess-svelte](https://github.com/carbon-design-system/carbon-preprocess-svelte) trims build times and bundle size with three drop-in tools: faster HMR in development, and leaner CSS and JavaScript when you ship. Each works on its own.

Add carbon-preprocess-svelte as a dev dependency:

```sh
# npm
npm i -D carbon-preprocess-svelte

# pnpm
pnpm add -D carbon-preprocess-svelte

# Yarn
yarn add -D carbon-preprocess-svelte

# Bun
bun add -D carbon-preprocess-svelte
```

### optimizeImports

Rewrites barrel imports to direct source paths, dramatically cutting cold build and HMR times.

### optimizeCss

Tree-shakes unused Carbon CSS at build time, often removing hundreds of kilobytes from production bundles. In typical apps it removes 60–80% of the stylesheet. Set `propAware: true` to also remove styles for prop values your app never passes, which removes over 80%.

### optimizeComponents

Rewrites each Carbon component your app renders for the props it passes, removing branches and child components that can never run. In typical apps it removes 18–27% of the production JavaScript. Your own wrapper components are rewritten the same way when every place that renders them is in `content`. It runs on production builds only, and fails the build if a module outside its `content` globs renders a Carbon component or a rewritten wrapper, including through a re-export or a dynamic `import()`. For Webpack and Rspack, use `OptimizeComponentsPlugin`.

```javascript
// vite.config.js
import { svelte } from "@sveltejs/vite-plugin-svelte";
import {
  optimizeComponents,
  optimizeCss,
  optimizeImports,
} from "carbon-preprocess-svelte";

export default {
  plugins: [
    optimizeComponents(),
    svelte({ preprocess: [optimizeImports()] }),
    optimizeCss({ propAware: true }),
  ],
};
```

### Configure your bundler

Add `optimizeImports` to your Svelte preprocessor and `optimizeCss` to your bundler plugins.

**Vite:**

```javascript
// vite.config.js
import { svelte, vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import { optimizeCss, optimizeImports } from "carbon-preprocess-svelte";

export default {
  plugins: [
    svelte({
      preprocess: [vitePreprocess(), optimizeImports()],
    }),
    optimizeCss(),
  ],
};
```

**SvelteKit:**

```javascript
// vite.config.js
import adapter from "@sveltejs/adapter-static";
import { sveltekit } from "@sveltejs/kit/vite";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import { optimizeCss, optimizeImports } from "carbon-preprocess-svelte";

export default {
  plugins: [
    sveltekit({
      preprocess: [vitePreprocess(), optimizeImports()],
      adapter: adapter(),
    }),
    optimizeCss(),
  ],
};
```

On SvelteKit 2, pass the same `preprocess` in `svelte.config.js` and the adapter under `kit`.

**Rollup:**

```javascript
// rollup.config.js
import svelte from "rollup-plugin-svelte";
import { optimizeCss, optimizeImports } from "carbon-preprocess-svelte";

const production = !process.env.ROLLUP_WATCH;

export default {
  plugins: [
    svelte({
      preprocess: [optimizeImports()],
    }),
    production && optimizeCss(),
  ],
};
```

**Webpack:**

Rspack implements Webpack's plugin and loader APIs, so the same configuration works in `rspack.config.mjs`.

```javascript
// webpack.config.mjs
import { OptimizeCssPlugin, optimizeImports } from "carbon-preprocess-svelte";

export default {
  module: {
    rules: [
      {
        test: /\.svelte$/,
        use: {
          loader: "svelte-loader",
          options: {
            preprocess: [optimizeImports()],
          },
        },
      },
    ],
  },
  plugins: [new OptimizeCssPlugin()],
};
```

## Icons and pictograms

The icon and pictogram sets ship as separate packages of individual Svelte components. Both are professionally designed, original iconography crafted to complement the scale and grid of Carbon components. Each is optional, so install only what you need.

### Icons

2,700+ icons designed for product UI, in four sizes (16, 20, 24, and 32 pixels), set with a single prop.

```sh
# npm
npm i carbon-icons-svelte

# pnpm
pnpm add carbon-icons-svelte

# Yarn
yarn add carbon-icons-svelte

# Bun
bun add carbon-icons-svelte
```

```svelte
<script>
  import Add from "carbon-icons-svelte/lib/Add.svelte";
</script>

<Add size={24} />
```

### Pictograms

1,500+ illustrative pictograms for empty states, onboarding, hero sections, and feature callouts. Larger than icons, 64px by default.

```sh
# npm
npm i carbon-pictograms-svelte

# pnpm
pnpm add carbon-pictograms-svelte

# Yarn
yarn add carbon-pictograms-svelte

# Bun
bun add carbon-pictograms-svelte
```

```svelte
<script>
  import Cloud from "carbon-pictograms-svelte/lib/Cloud.svelte";
</script>

<Cloud />
```

## Documentation for LLMs

Documentation is available in LLM-friendly plain text for use with coding assistants, plus a standalone Markdown document for every component. Append `.md` to any component's URL to read it.

- **[llms.txt](/llms.txt)** — A component index where each entry links to its per-component Markdown doc, sized for model context windows.
- **[llms-full.txt](/llms-full.txt)** — The full component documentation in a single plain-text file.

## Collection

The Carbon Svelte collection includes packages for icons, pictograms, and data visualization:

- **Carbon Components Svelte** — 100+ components — [GitHub](https://github.com/carbon-design-system/carbon-components-svelte)
- **Carbon Icons Svelte** — 2,700+ icons — [GitHub](https://github.com/carbon-design-system/carbon-icons-svelte)
- **Carbon Pictograms Svelte** — 1,500+ pictograms — [GitHub](https://github.com/carbon-design-system/carbon-pictograms-svelte)
- **Carbon Charts Svelte** — 25+ charts, powered by d3 — [GitHub](https://github.com/carbon-design-system/carbon-charts/tree/master/packages/svelte)
- **Carbon Preprocess Svelte** — Svelte preprocessors and build plugins — [GitHub](https://github.com/carbon-design-system/carbon-preprocess-svelte)

---

### Package.Json (`docs/package.json`)

{
  "private": true,
  "scripts": {
    "dev": "bun run format-component-api && bun run --parallel index-docs dev:vite",
    "dev:vite": "vite dev",
    "build": "bun run format-component-api && bun run --parallel build:index-docs build:sitemap && bun run build:md-docs && bun run build:vite",
    "index-docs": "bun scripts/index-docs.ts",
    "build:index-docs": "bun scripts/index-docs.ts",
    "build:md-docs": "bun scripts/generate-md-docs.ts",
    "build:sitemap": "bun scripts/generate-sitemap.ts",
    "format-component-api": "bun scripts/format-component-api.ts",
    "build:vite": "vite build",
    "typecheck:examples": "bun scripts/typecheck-examples.ts",
    "preview": "vite preview"
  },
  "devDependencies": {
    "@roxi/routify": "^3.6.4",
    "@sveltejs/vite-plugin-svelte": "^7.3.1",
    "@types/mdast": "^4.0.4",
    "@types/prismjs": "^1.26.5",
    "@types/unist": "^3.0.3",
    "carbon-icons-svelte": "^13.15.0",
    "carbon-pictograms-svelte": "^13.20.0",
    "clipboard-copy": "^4.0.1",
    "estree-walker": "^3.0.3",
    "github-slugger": "^2.0.0",
    "mdsvex": "^0.12.8",
    "minisearch": "^7.1.0",
    "prettier": "^3.9.9",
    "prettier-plugin-svelte": "^4.1.1",
    "prismjs": "^1.30.0",
    "rehype-slug": "^6.0.0",
    "svelte": "^5.57.1",
    "unist-util-visit": "^5.1.0",
    "vite": "^8.3.1"
  },
  "type": "module"
}

---

### Release Screenshots (`docs/release-screenshots.md`)

# Release screenshots

Recipe for turning the `` and `` markers in a GitHub release body into 4x PNGs. It was first run for [v0.113.0](https://github.com/carbon-design-system/carbon-components-svelte/releases/tag/v0.113.0). Run it as part of the [post-release checklist](../CONTRIBUTING.md#post-release-checklist).

Each marker follows a fenced `svelte` snippet. The goal is for the image to be what that snippet renders, so every snippet is compiled verbatim, bundled with the repo's own `src/` and `css/all.css`, and captured in your installed Chrome. It is a Bun script with no dependencies beyond what the repo already has: no Playwright, no Vite, and nothing written into `e2e/`. All 15 v0.113.0 shots take about 3.5 seconds cold and well under a second when cached.

This is for running locally. It needs Chrome (or Chromium) installed, and Bun 1.4 or newer for `Bun.WebView`.

## Prerequisites

- `css/all.css` exists. It is gitignored and built by `bun setup`; rebuild it with `bun run build:css` if your checkout is stale.
- Chrome is installed. Bun finds it automatically; set `BUN_CHROME_PATH` to use another binary.
- `gh` is authenticated, if you pass a tag. (A saved Markdown file works without it.)

## Steps

1. **Add a recipe for each new marker** in [`scripts/lib/release-screenshot-recipes.ts`](../scripts/lib/release-screenshot-recipes.ts) (see [Recipes](#recipes)). A snippet with no recipe is captured as mounted at 420px wide, which is right for static components.

2. **Run it.** Pass the release tag and the script fetches the notes with `gh`, or pass a saved Markdown file.

   ```sh
   bun run release:screenshots vX.Y.Z                 # all
   bun run release:screenshots vX.Y.Z meter box       # some
   bun run release:screenshots notes.md --scale 2 --out /tmp/shots --jobs 2
   ```

   Output goes to the gitignored `release-screenshots/<name>.png`. `--jobs` sets how many Chrome views capture at once (default 4, or fewer on a smaller machine). Unchanged shots are reused from a cache (see [Caching](#caching)); `--no-cache` forces a fresh render. A `SKIP` line means no `svelte` fence precedes that marker. Keep the markers in the body while drafting; strip them from the published notes when you replace them with images.

3. **Check the output.** Compare each image against the marker's description, not just "it rendered". Previews of 4x images can exceed viewer size limits; downscale a copy to look, never the original: `sips -Z 900 in.png --out /tmp/in.png`.

4. **Attach the images** by dragging the PNGs into the GitHub release editor, then delete the marker comments. Do not commit them.

## How it works

1. `extractSnippets` pairs each marker with its preceding `svelte` fence. The fences are written to `node_modules/.cache/release-screenshots/<name>.svelte`, under the repo so `svelte` resolves. GitHub returns CRLF line endings; they are normalized first (every fence regex fails otherwise).
2. One `Bun.build` bundles all snippets as lazy modules chosen by `?s=<name>`. A small plugin compiles `.svelte` files with `svelte/compiler` and aliases `carbon-components-svelte` to `src/` and its `css/` path to `css/`.
3. `Bun.serve` serves the bundle from memory on a free port.
4. Chrome and its views start while the bundle builds. Each view (`--jobs` of them, in one Chrome) pulls shot names off a shared queue, so shots run side by side. A single view allows one CDP call at a time, which is why parallelism comes from views, not from overlapping calls on one. For each shot, a view sets the viewport and `deviceScaleFactor` through `Emulation.setDeviceMetricsOverride`, loads the page, waits for fonts and running animations, runs the recipe, then crops `Page.captureScreenshot` to the `#app` box.

The browser is always spawned fresh (`url: false`). Without that, Bun attaches to a Chrome you already have open with remote debugging, and macOS prompts you to allow it.

## Caching

Each shot is cached under `node_modules/.cache/release-screenshots/png`, keyed on a hash of its snippet, recipe (options and `act` source), `--scale`, the library (`src/` and `css/all.css`), and the capture scripts themselves. Edit one recipe and only that shot re-renders. If every shot is cached, Chrome never starts (about 0.2s). Change anything in `src/`, rebuild the CSS, or edit the scripts and everything re-renders.

Set `volatile: true` on a recipe whose output depends on the clock, like `relative-time`, so it is always re-rendered.

## Recipes

Set per-shot options in `recipes`:

| Option | Use |
| --- | --- |
| `width` | `#app` width in CSS px (default 420). Wider for tables and page headers. |
| `viewport` | Capture the whole viewport instead of `#app`. For fixed-position overlays such as `GridOverlay`; also zeroes `#app` padding. |
| `volatile` | Never cache this shot. |
| `act(view)` | Interaction to run before capture. |

Helpers for `act`, from [`scripts/lib/release-screenshots.ts`](../scripts/lib/release-screenshots.ts):

| Helper | Does |
| --- | --- |
| `css(v, text)` | Inject a stylesheet. Use it to reserve room for popups (`#app{min-height:...}`). |
| `tag(v, selector, regex, nth)` | Find an element by text, `aria-label`, or `title`, and return a selector `v.click` can use. |
| `quiet(v, blur)` | Move the mouse away, and blur the active element unless `blur` is `false`. |
| `settle(v)` | Wait for fonts, running animations, and two frames. Runs before and after every `act` already. |
| `waitFor(v, selector)` | Resolve once the selector matches. |
| `v.click(selector, { modifiers })` | Real click with an actionability wait. |
| `v.cdp(method, params)` | Any CDP command, for hover and anything else `WebView` lacks. |

Patterns that came up:

| Marker says | Do |
| --- | --- |
| Pressed or selected state | `v.click(await tag(...))`, then `quiet(v)`. |
| Menu or popover open | Click the trigger, `css` a `min-height` on `#app`, then `quiet(v, false)` to keep the focused menu item. |
| Video: "move to the next slide" and similar | Capture the end state of the described interaction. Screenshots stand in for videos. |
| Virtualized list | Find the scroller (`scrollHeight > clientHeight`) and set `scrollTop` to `itemHeight * row`. |
| flatpickr | Click the input, wait for `.flatpickr-calendar.open`, then `tag` the day cells (exclude `.prevMonthDay` and `.nextMonthDay`). Pass `modifiers: ["Shift"]` for shift-click. A `min-height` of about 470. |
| Tooltip on hover | See the gotcha below. |
| Several sibling components | Mount as written. If they stack too tightly, lay them out in a row with `css` and say so in the PR. |

## Gotchas

- **4x, PNG, no downscaling.** `deviceScaleFactor: 4` renders text and edges natively at 4x; it is not an upscale. The files are small (7 to 90 KB) because the content is flat color and text.
- **`WebView` has no element screenshot or scale option.** [`release-screenshots.ts`](../scripts/release-screenshots.ts) does both over CDP. Round the clip height up (`Math.ceil`), or Chrome crops a CSS pixel and the image no longer matches an element-screenshot render.
- **`cdp()` needs a session.** `navigate("about:blank")` once before the first `cdp` call. Each view also allows one `cdp` or `evaluate` call in flight, so do not `Promise.all` them.
- **Wait on animations, not timers.** `settle` awaits `document.getAnimations()`, so menus and calendars are captured after their open animation, with no fixed sleeps.
- **Focus rings and hover.** A click leaves the clicked element focused, and the mouse stays over it. Call `quiet` before capture unless the marker asks for focus. Menus are the exception: keep the focus ring on the first item, which is what the component does when opened.
- **Native `title` tooltips are not rendered headlessly.** `RelativeTime` uses `title`. Draw an equivalent element from the real `title` value (see the recipe) and say in the PR that it is simulated.
- **Capped menus.** `MultiSelect`'s menu stops at about 5.5 rows, so a marker that names every group header needs `max-height: none` on `.bx--list-box--expanded .bx--list-box__menu`. This changes the default rendering. Disclose it.
- **Element screenshots clip popups.** Reserve space with `min-height` on `#app`; the clip is the `#app` box.
- **Do not change the markup to look better.** Layout-only CSS on `#app` (width, flex row) is fine. Anything that edits the snippet or the component defeats the point; flag every deviation when you hand the images over.
- **Svelte 5 compiles these snippets in legacy mode**, so `let:`, `slot=`, `<svelte:fragment slot>`, and `export let` all work as written in release notes.
- **Use a fixed viewport width** (`width + 64`) so wrapping is reproducible. `GridOverlay` is `pointer-events: none` and sized to the viewport, so capture it at a real breakpoint width (1056 is `lg`).
- **Timestamps differ between runs.** `RelativeTime`'s tooltip shows the current time minus five minutes. That is the only expected diff when re-running a release.

---

### Tsconfig.Json (`docs/tsconfig.json`)

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "allowImportingTsExtensions": true,
    "paths": {
      "carbon-components-svelte": ["../src"],
      "carbon-components-svelte/tokens": ["../src/tokens/index.js"],
      "carbon-components-svelte/*": ["../*"],
      "*": ["./*"]
    }
  },
  "include": ["src/**/*", "scripts/**/*", "*.ts"]
}

---

### Public Robots (`docs/public/robots.txt`)

User-agent: *
Allow: /

Sitemap: https://svelte.carbondesignsystem.com/sitemap.xml

---

--- METRICS ---
- Files Extracted: 7
- Estimated Token Budget: ~13702 tokens
- Canonical Reference: https://codewiki.google/github.com/carbon-design-system/carbon-components-svelte
