AZMARA PLATFORM
Components

Icon

Renders Lucide, Tabler or your own SVG with consistent size, stroke, motion and accessibility. A Server Component.

Usage

import { Icon } from "@azmr/ui/icons";
import { Search } from "lucide-react";

<Icon icon={Search} />
Preview theme
Preview density
import { Icon } from "@azmr/ui/icons";
import { Bell, LoaderCircle, Search } from "lucide-react";

export default function IconBasic() {
  return (
    <div className="flex flex-col gap-4">
      <div className="flex items-end gap-4">
        <Icon icon={Search} size="xs" />
        <Icon icon={Search} size="sm" />
        <Icon icon={Search} size="md" />
        <Icon icon={Search} size="lg" />
        <Icon icon={Search} size="xl" />
        <Icon icon={Search} size={40} />
      </div>
      <div className="flex items-center gap-4">
        {/* Decorative by default; aria-label makes it a labelled image. */}
        <Icon icon={Bell} aria-label="Notifications" size="lg" />
        <Icon icon={Bell} size="lg" strokeWidth={1} />
        <Icon icon={Bell} size="lg" strokeWidth={2.5} />
        {/* spin keeps moving under reduced motion: a frozen loader reads as a hung page. */}
        <Icon icon={LoaderCircle} size="lg" spin aria-label="Loading" />
      </div>
    </div>
  );
}

Icon takes the icon component you pass it, so only the icons you import are bundled (this is tested). Lucide is the default set; Tabler and your own SVG components work through the same icon prop with no adapter. Sizes xs to xl follow the size tokens, and a number is a pixel size.

An icon is decorative by default (aria-hidden). Give it aria-label or aria-labelledby to make it a labelled image. spin is for loaders and, like Spinner, keeps moving under reduced motion.

By name

Preview theme
Preview density
import { Icon } from "@azmr/ui/icons/lucide";

export default function IconNamed() {
  return (
    <div className="flex items-center gap-4">
      <Icon name="house" size="lg" />
      <Icon name="circle-alert" size="lg" aria-label="Warning" />
      <Icon name="trash-2" size="lg" aria-label="Delete" />
      <Icon name="chevrons-up-down" size="lg" />
    </div>
  );
}

The curated set at @azmr/ui/icons/lucide gives about 60 everyday icons by Lucide's own kebab-case names, with the names type-checked. For your own typed set use createIconSet, and for custom SVGs registerIcons:

import { createIconSet, registerIcons } from "@azmr/ui/icons";

export const { Icon } = createIconSet({ check: Check, "my-logo": MyLogo });

registerIcons({ "my-logo": MyLogo }); // global, untyped names: <Icon name="my-logo" />

An unknown name renders nothing and warns once in development; it never crashes a page.

Props

PropTypeDescription
iconcomponentThe icon to render. Wins over name.
namestringA name from the registry (registerIcons).
size"xs" to "xl" or numberDefault md.
strokeWidthnumberStroke width of line icons.
spinbooleanContinuous rotation for a loading indicator.
aria-label / aria-labelledbystringMakes the icon meaningful (role="img").
classNamestringMerged last: size-8 replaces the size token.

Migrating from Material icons

@azmr/ui/icons/mui-map maps over 300 @mui/icons-material names (including the Outlined, Rounded, Sharp and TwoTone variants) to Lucide and Tabler, and lists the common ones it deliberately does not map. The migration codemod uses it.

Accessibility

  • Decorative icons are hidden from assistive technology; meaningful ones are role="img" with a name.
  • An icon-only button is named by the labelled icon inside it, or by the button's own aria-label.
  • Licences: Lucide is ISC, Tabler is MIT. react-icons is deliberately not bundled.

On this page