docs/guide/icons
Core concepts · 2,026 Lucide icons

Icon Gallery

Search, preview and copy zenit’s public icons, and see how static constants, the dynamic registry and the semantic layer divide the work.

8 min read · full searchable catalog

Two entry points

Public builds ship Lucide (lucide-static v1.31.0). The full provider catalog is ui.icons with 2,026 names: 2,025 upstream SVGs plus the compatibility alias hand-pointing (byte-identical to upstream pointer.svg). ui.system_icons offers 45 provider-neutral semantic roles; zenit’s own components depend only on those, so no vendor names leak into the public component contract.

Entry pointUse it for
ui.system_icons45 stable semantic roles (close, search, alert, pointer…); for reusable framework / package code that must compile with any provider
ui.icons2,026 Lucide names; for product UI and dynamic configuration
ui.assets.common17 legacy compatibility icons; not preferred for new code
ICON PATH
Semantic role -> provider mapping -> Asset -> icon render path. Swapping providers replaces only the mapping module.
The semantic-icon story in the real zenit Storybook: a virtual cursor sweeps roles, sizes and tints; the full provider catalog is in the web list below. /components/icons

All 2,026 icons

Type to filter instantly; icons load 180 at a time so the page never creates two thousand DOM nodes at once. Click any icon for a ready-to-paste ui.iconTint call; names that need escaping (such as ui.icons.@"type") follow the generator’s rules.

180 / 2,026

Rendering and tint

At build time gen-icons turns each icon into flattened geometry (every Asset carries an icon_id and size-specific reps). At runtime ui.iconTint picks the rep closest to the logical size and writes it to the node. No SVG engine runs in the window; only an Asset without pre-flattened geometry falls back to svgTint rasterization. When width / height aren’t fixed pixels, the Asset’s default size applies (24 for Lucide).

ui.iconTint is the best fit for theme-following colors; one Asset can be reused at any size and color. ui.icon takes no tint and draws white, so reach for iconTint on light backgrounds. Use ui.iconTintStyled when you need BoxStyle fields such as opacity. To recolor a mounted icon (hover, selected state), call node.setTint(color): it handles both icon-table storage and the image storage of the svgTint fallback, and returns false when the node has neither.

icons.zig
// Follow the theme: tint with a token color.
const settings = try ui.iconTint(
    cx,
    ui.icons.settings,
    cx.tokens.color.fg_secondary,
    .{ .width = .fixed(20), .height = .fixed(20) },
);

// Dynamic lookup by kebab-case source name (config files, user data).
const configured = ui.icons.get("circle-alert") orelse
    return error.UnknownIcon;
const warning = try ui.iconTint(cx, configured, cx.tokens.color.warning, .{
    .width = .fixed(24),
    .height = .fixed(24),
});

// Enumerate the whole registry.
for (ui.icons.all) |entry| {
    std.log.debug("{s}", .{entry.name});
}

The semantic icon contract

Component code should say ui.system_icons.close rather than bind to a vendor’s name. The Lucide profile maps all 45 roles to concrete Assets in src/ui/system_icons_lucide.zig; switching providers with -Dicon-set leaves the component API unchanged.

activity
alert
audio
check
close
search
heart
star
home
settings
notification
calendar
user
mail
trash
download
upload
edit
copy
lock
chevron_down
chevron_up
chevron_left
chevron_right
minus
plus
more_horizontal
cursor_default
cursor_click
pointer
move
not_allowed
grab
resize_horizontal
resize_vertical
resize_diagonal
wait
progress
help
warning
info
chevrons_up
pause
sun
moon

Roles whose Lucide name differs:

ui.system_iconsLucide source name
alertcircle-alert
audioheadphones
closex
homehouse
notificationbell
trashtrash-2
editpencil
more_horizontalellipsis
cursor_defaultmouse-pointer-2
cursor_clickmouse-pointer-click
pointerhand-pointing
not_allowedban
grabhand
resize_horizontalchevrons-left-right
resize_verticalchevrons-up-down
resize_diagonalexpand
waithourglass
progressloader-circle
helpcircle-help
warningtriangle-alert
semantic.zig
// Reusable code: depend on the role, not the provider's name.
const close = try ui.iconTint(cx, ui.system_icons.close, cx.tokens.color.fg_secondary, .{
    .width = .fixed(16),
    .height = .fixed(16),
});

// Icon-only button: no visible label, so set the accessible name explicitly.
const remove = try ui.widgets.Button(.{
    .icon_only = true,
    .icon_asset = ui.system_icons.trash,
    .variant = .ghost,
}).mount(scope, cx);
remove.behavior.interaction.a11y.label = "Delete";

Naming, updates and license

  • ✓

    Two spellings. Registry lookups use Lucide’s kebab-case source name (ui.icons.get("circle-alert")); Zig constants swap hyphens for underscores (ui.icons.circle_alert).

  • ✓

    Automatic escaping. Constants that collide with Zig keywords or primitive type names (including iN / uN) or start with a digit are escaped by the generator, for example ui.icons.@"type"; dynamic get is unaffected.

  • ✓

    Enumerable. ui.icons.all is a name-sorted array of { name, asset } entries; get binary-searches it.

  • ✓

    Identical to upstream. SVG files are byte-identical to upstream for clean updates and license audits; delete the old files before copying so renamed icons don’t linger.

  • ✓

    Separately licensed. Lucide is ISC-licensed (src/ui/icons_oss/LICENSE) and not covered by zenit’s root GPL-3.0-only license.

update & verify
# Verify the Lucide profile (test-headless + storybook build); the worktree is not modified
bash scripts/switch_icon_set.sh lucide

# Regenerate the committed provider module after the SVG set changes
zig build gen-icons-lucide
zig build test-ui
zenit · Dual-licensedFree for open-source projects under GPL-3.0-only; closed-source or commercial products need a commercial license.Contact the author: zongyi.xzy#gmail.com (replace # with @)zenit 4c50245 2026-10-01