Setup

Install the icons, render them in your framework, preload the sprite, or replace the set with your own.

Installation

pnpm add @isbatak/icons

Every 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>
  )
}

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.

  1. 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 FileConfig

    Run ikona. It writes public/sprite.svg and the union of your icon names to .ikona/types/icon-name.d.ts.

  2. Point the icons at your sprite, once, before anything renders:

    import { setIconSprite } from "@isbatak/icons"
    
    setIconSprite("/sprite.svg")
  3. 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 a rocket.svg, and a built-in name like check is an error until you add your own check.svg.

Components draw their own icons from the active sprite too, so your sprite has to include these names, exported as componentIconNames:

NameUsed by
checkSelect item indicator
chevron-downSelect indicator
closeClose Button, Select clear trigger, Search Input
lockPassword Input
searchSearch Input
viewPassword Input, while the password shows
view-offPassword 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.