Setup
Install the icons, render them in your framework, preload the sprite, or replace the set with your own.
Installation#
pnpm add @isbatak/iconsEvery UI package has an Icon. It renders <svg><use href="sprite.svg#name" /></svg>, styled by the design system's
icon recipe.
import { Icon } from "@isbatak/react-ui/icon"
export function NextButton() {
return (
<button type="button">
Next <Icon name="arrow-right" size="sm" />
</button>
)
}<script setup lang="ts">
import { Icon } from "@isbatak/vue-ui/icon"
</script>
<template>
<button type="button">Next <Icon name="arrow-right" size="sm" /></button>
</template><script lang="ts">
import { Icon } from "@isbatak/svelte-ui/icon"
</script>
<button type="button">Next <Icon name="arrow-right" size="sm" /></button>import { Icon } from "@isbatak/solid-ui/icon"
export function NextButton() {
return (
<button type="button">
Next <Icon name="arrow-right" size="sm" />
</button>
)
}import { Icon } from "@isbatak/preact-ui/icon"
export function NextButton() {
return (
<button type="button">
Next <Icon name="arrow-right" size="sm" />
</button>
)
}import "@isbatak/lit-ui/icon"
import { html, LitElement } from "lit"
class NextButton extends LitElement {
render() {
return html`<button type="button">Next <ib-icon name="arrow-right" size="sm"></ib-icon></button>`
}
}
customElements.define("next-button", NextButton)import { spriteUrl } from "@isbatak/icons"
import { icon } from "@isbatak/panda-ds/recipes"
const button = document.querySelector("button")!
button.insertAdjacentHTML(
"beforeend",
`<svg aria-hidden="true" class="${icon({ size: "sm" })}"><use href="${spriteUrl}#arrow-right"></use></svg>`,
)Preload the sprite#
Icons inside the sprite can't start loading until the browser finds the first <use>. Preload it so they show up with
the first paint. Render IconSpritePreload once, near the top of the page:
import { IconSpritePreload } from "@isbatak/react-ui/icon"
export default function RootLayout({ children }) {
return (
<html>
<body>
<IconSpritePreload />
{children}
</body>
</html>
)
}In React it calls preload() from react-dom, which puts the <link> in the head; Svelte's version uses
<svelte:head>. With Lit or no framework, call preloadIconSprite() from @isbatak/icons instead. All of them preload
whichever sprite is active, including your own.
Use your own icons#
You can replace the built-in icons with your own set. Every Icon then draws from your sprite, and name accepts your
icon names instead of ours. It's all or nothing: the built-in icons are no longer used.
-
Build a sprite from a folder of SVGs with ikona:
import type { FileConfig } from "@ikona/cli" export default { outputDir: ".ikona", icons: { inputDir: "./icons", spriteOutputDir: "./public", optimize: true, }, } satisfies FileConfigRun
ikona. It writespublic/sprite.svgand the union of your icon names to.ikona/types/icon-name.d.ts. -
Point the icons at your sprite, once, before anything renders:
import { setIconSprite } from "@isbatak/icons" setIconSprite("/sprite.svg") -
Swap the name type for yours:
import type { IconName as AppIconName } from "./.ikona/types/icon-name" declare module "@isbatak/icons" { interface IconRegistry { iconName: AppIconName } }Now
<Icon name="rocket" />type-checks if you have arocket.svg, and a built-in name likecheckis an error until you add your owncheck.svg.
Components draw their own icons from the active sprite too, so your sprite has to include these names, exported as
componentIconNames:
| Name | Used by |
|---|---|
check | Select item indicator |
chevron-down | Select indicator |
close | Close Button, Select clear trigger, Search Input |
lock | Password Input |
search | Search Input |
view | Password Input, while the password shows |
view-off | Password Input, while the password is hidden |
name always accepts them, even when your IconRegistry doesn't list them.
IconSpritePreload and preloadIconSprite() pick up the new sprite on their own.