carbon-icons-svelte turns Carbon Design System SVG icons into Svelte components. The build reads @carbon/icons metadata, writes one .svelte file per icon to lib/, and emits a barrel export plus TypeScript definitions.
README.md documents import paths, props, and TypeScript usage. This file documents how the repo is built and how to change it.
Not sure what to build? File an issue before opening a PR.
- Bun. The pinned version is in
.bun-version. CI reads it.
Bun handles packages, tests, and builds. You don't need a separate Node toolchain. Run scripts with bun <script> and one-off binaries with bunx <bin>.
Fork the repo and clone your fork:
git clone <YOUR_FORK>
cd carbon-icons-svelteSet the original repository as upstream:
git remote add upstream git@github.com:carbon-design-system/carbon-icons-svelte.git
# verify that the upstream is added
git remote -vInstall dependencies:
bun install| Script | What it does |
|---|---|
bun test |
Run all tests. prepack calls this, so it's also the full build. |
bun run prepack |
Same as bun test. Builds lib/, regenerates ICON_INDEX.md and docs assets, runs snapshot and unit tests. |
bun run build:docs-assets |
Run buildIcons() only, no tests. Used by the docs site's predev / prebuild hooks. |
bun run test:types |
Link the local package and run svelte-check in tests/svelte@4/ and tests/svelte@5/. |
When iterating locally, scope test runs to what you changed. The full suite regenerates thousands of icon files and takes a while.
src/index.tsimportsmetadata.jsonfrom the pinned@carbon/iconsversion inpackage.jsondevDependencies. It merges deprecated icons from older pinned packages and applies rename aliases (see Backwards compatibility).- Each icon in metadata can ship multiple size outputs (
Add16,Add20, …). The build strips the size suffix and deduplicates to one Svelte component per icon name. template.tsturns anIconOutputdescriptor into a Svelte component string: props, accessibility attributes, SVG markup.templateSvgproduces the compact inline SVG for the docs preview grid.buildIcons()writes:lib/<IconName>.svelte, one component per iconlib/<IconName>.svelte.d.ts, per-icon type stub re-exporting from the barrellib/index.jsandlib/index.d.ts, barrel export andCarbonIconPropstypeICON_INDEX.md, committed icon listdocs/public/build-info.<hash>.jsonanddocs/src/generated/build-info-url.ts, hashed preview data for the docs site (gitignored, regenerated each build)
buildIcons() is the only entry point. Tests call it directly. prepack runs those tests. build:docs-assets calls it for the docs app. src/global.d.ts holds local type declarations for @carbon/icons metadata JSON imports.
lib/ is gitignored. Only the generator source, snapshots, and ICON_INDEX.md get committed.
@carbon/icons sometimes removes or renames icons between minor versions. We don't remove icons in minor releases of this library. src/index.ts enforces that with two tables.
When Carbon drops an icon, add it to DEPRECATED_ICONS with the @carbon/icons version that still has it:
const DEPRECATED_ICONS: Record<string, MetadataSource> = {
FoundationModel: metadata_11_31,
// ...
};Pin the source version as a separate devDependency alias (e.g. "@carbon/icons-11.31": "npm:@carbon/icons@~11.31.0"), import its metadata.json, and add a matching declare module in src/global.d.ts if TypeScript complains. The build pulls matching icons from the older metadata so existing imports keep working.
When Carbon renames an icon, map the old export name to the new one in RENAMED_ICONS:
const RENAMED_ICONS = {
NavaidVordme: "NavaidVorDme",
// ...
} as const;The build then exports the old name from the barrel, writes an alias .svelte that forwards props to the new component (or re-exports directly when old and new names collide on case-insensitive filesystems; see collidesOnCaseInsensitiveFs), lists the alias in ICON_INDEX.md as (alias of <newName>), and puts renamedIcons in the docs build-info payload.
Don't remove a DEPRECATED_ICONS or RENAMED_ICONS entry in a minor release. That's a breaking change. Save it for a major bump and note it in the changelog.
Svelte 4 and 5 are supported in 13.x. Type-check coverage lives in tests/svelte@4/ and tests/svelte@5/.
For Svelte 3, pin carbon-icons-svelte@12.13.0. Template changes shouldn't break Svelte 4 barrel imports or CarbonIconProps.
No repo-wide linter. Match what's already in src/ and tests/.
tsconfig.jsonsetsstrict: true. Keepsrc/andtests/type-clean.- Use
node:import specifiers:import { createHash } from "node:crypto", not"crypto". - ESM only.
package.jsonhas"type": "module". Import JSON withimport … with { type: "json" }. - Hoist regexes to module scope as named
constvalues with a short comment (SIZE_PATTERN,GLYPH_SUFFIX_PATTERNinsrc/index.ts). - Batch file writes with
Promise.allinbuildIcons, not sequential awaits. - Don't hand-edit generated output. Never commit
lib/,docs/public/build-info*.json, ordocs/src/generated/. Change the generator and re-runbun test. - A change to
src/template.tschanges every emitted icon. Treat it like a public API change.
Tests use Bun. They live in tests/.
bun test # everything, includes full rebuild
bun test template # filter by file-path substring
bun test tests/template.test.ts # a single filetests/index.test.ts calls buildIcons(), checks the total icon count, and snapshots the sorted export names in tests/__snapshots__/index.test.ts.snap.
When icons are added or removed (including RENAMED_ICONS aliases), update the count:
expect(icons.length).toEqual(2689);Run bun test. If the name list changed, read the snapshot diff before regenerating. Use bun test --update-snapshots on purpose. A snapshot diff is a behavior change.
tests/template.test.ts pins template and templateSvg for a few inputs: standard icon, glyph, empty content, missing descriptor fields. Add a case when you change template logic instead of relying on the full icon snapshot.
tests/test-types.ts (bun run test:types) runs bun link at the repo root, then installs and runs svelte-check in each tests/svelte@* project. Those projects import from the barrel, import carbon-icons-svelte/lib/<Icon>.svelte directly, use CarbonIconProps, and check ComponentProps on Svelte 5 vs satisfies CarbonIconProps on Svelte 4.
Run bun run prepack first so lib/ exists, then bun run test:types. CI runs both.
Most releases are just a dependency bump for new Carbon icons.
- Bump
devDependencies["@carbon/icons"]inpackage.json. bun install- Check what Carbon added, removed, or renamed. Grep the new
metadata.jsonif you need to. - Handle removals and renames before building (Backwards compatibility).
bun run prepack- Update the icon count in
tests/index.test.tsif the total changed. - Review
ICON_INDEX.md, the snapshot, and any new alias files inlib/locally (lib/is not committed). - Commit with a conventional message (below).
CI runs this. Run it locally too:
bun install
bun run prepack # build lib/, snapshot test, template tests
bun run test:types # svelte-check against Svelte 4 and 5All three must pass. If prepack fails on the icon count, the snapshot moved, or a rename alias target is missing, fix the generator or compatibility tables before updating snapshots.
Most PRs here are @carbon/icons bumps. A clean upgrade (no upstream removals or renames) is a small diff. See #229 (11.81 → 11.82, net +2 icons).
Branch. Name it after the target version, e.g. upgrade-11.82.
Commands.
git fetch upstream
git checkout master
git merge upstream/master
git checkout -b upgrade-11.82
# edit package.json: bump devDependencies["@carbon/icons"]
bun install
bun run prepack
# if the count assertion fails, update tests/index.test.ts, then re-run
bun run test:types
git add package.json bun.lock ICON_INDEX.md tests/
git commit -m "feat(deps-dev): upgrade \`@carbon/icons\` 11.81 -> 11.82"
git push -u origin upgrade-11.82Files that should change in a clean upgrade:
| File | What changed |
|---|---|
package.json |
@carbon/icons version in devDependencies |
bun.lock |
Lockfile from bun install |
ICON_INDEX.md |
Regenerated list; header line updates the Carbon version and canonical icon count |
tests/index.test.ts |
expect(icons.length) matches the new total (includes rename aliases) |
tests/__snapshots__/index.test.ts.snap |
New or removed export names |
Files that should not be committed: lib/ (gitignored build output), docs/public/build-info*.json, docs/src/generated/. bun run prepack writes them locally; CI and the docs deploy hook regenerate them.
Title. Match the commit:
feat(deps-dev): upgrade `@carbon/icons` 11.81 -> 11.82
Body. One bullet under Features is enough for a clean bump. Use the canonical count from the ICON_INDEX.md header for the net delta:
**Features**
- upgrade `@carbon/icons` to v11.82.0 (net +2 icons)Compute net icons as new header count minus old (e.g. 2677 − 2675 = +2 in #229). If Carbon only changed existing SVGs and the count is unchanged, say so: (no new icons).
When it's not a clean upgrade. If Carbon removed or renamed icons, don't ship only the version bump. Update DEPRECATED_ICONS or RENAMED_ICONS in src/index.ts first (Backwards compatibility), then run the workflow above. Renames may also touch src/global.d.ts or docs preview code. Those changes can live in the same PR as the bump or in a preceding PR (see #227 for renames without a version bump).
Changelog. Contributors don't need to edit CHANGELOG.md. Maintainers add the entry at release time.
Review checklist. Before requesting review, confirm locally:
bun run prepackpasses (build, icon count, snapshot).bun run test:typespasses (Svelte 4 and 5).- The snapshot diff lists only the icons you expect from Carbon's release notes or metadata diff.
ICON_INDEX.mdheader version matchespackage.json.
docs/ is a Vite + Svelte app that previews every icon. Separate package.json and lockfile:
cd docs
bun install
bun devpredev and prebuild call bun run --cwd .. build:docs-assets to regenerate preview data. The site loads BUILD_INFO_URL, a content-hashed JSON file, for the icon grid, search, and rename aliases. Icon-only dependency bumps usually don't need docs changes. Changes to build-info shape or preview behavior do.
.github/workflows/ci.yml runs on every PR and on pushes to master:
bun cibun run prepackbun run test:types
Pushes to master also hit a Render deploy hook for the preview site.
<type>(<scope>): <subject>
Common types: fix, feat, docs, chore, test, ci, refactor. Scope is the area touched: deps-dev, docs, types, template, or omit for repo-wide changes. Append ! after the scope for breaking changes. One line, imperative mood. Put detail in the body; reference issues with Fixes #N.
Icon upgrades:
feat(deps-dev): upgrade `@carbon/icons` 11.81 -> 11.82
Removed or renamed upstream icons:
chore: account for renamed icons
Releases (maintainers):
v13.12.0
From the log:
fix(types): widen `size` prop to `number`
ci: use bun ci for reproducible builds
docs: hash build-info.json and remove from source control
For @carbon/icons upgrades, follow Open a pull request above. That's the path you'll use most of the time.
For other changes, sync your fork, branch from master, and open a PR against origin/master:
git fetch upstream
git checkout master
git merge upstream/master
git checkout -b my-changeKeep PRs focused. Match the commit message style.
Maintainers only from here.
release.yml publishes to NPM with provenance when a v* tag is pushed. It installs, runs bun run prepack, prunes with bunx culls --preserve=svelte, and runs npm publish --provenance --access public.
Bump the version in package.json, update CHANGELOG.md, then:
bun install
bun run prepack
git commit -am "v13.12.0"
git tag v13.12.0
git push origin v13.12.0If the workflow succeeds, the new version is on NPM.
After the package is on NPM:
- Create a new release on GitHub. Click "Generate release notes", then drop CI-only noise.
- Mark it as the latest release.