Skip to content

Icon

Iconify icons and your own SVGs, typed and inlined in the HTML at build time.

Usage

Search the project

meeko
<p><UIIcon name="lucide:search" /> Search the project</p>
<UIButton variant="outline">
  <UIIcon name="lucide:trash-2" label="Delete" />
</UIButton>

UIIcon writes an inline <svg>. Each literal name is resolved during the build with the collection package installed in the project: there are no runtime requests or CDN, and the bundle only carries the icons you use.

Installation and collections

You need @meekojs/meeko >=1.8 <2, with modules registered by name. Install the module and each Iconify collection you want to use as @iconify-json/<collection>. The module is registered by its name and its options go in the icon key, typed in meeko.config.ts.

ts
// pnpm add @meekojs/icon && pnpm add -D @iconify-json/lucide
// meeko.config.ts
export default defineMeekoConfig({
  modules: ['@meekojs/icon'],
  icon: {
    collections: ['lucide'], // @iconify-json/lucide of the project
    local: 'app/icons', // your own SVG files: 'local:<name>'
    size: '1em',
    include: ['lucide:sun', 'lucide:moon'], // for bind:name
  },
})

The available names form a TypeScript union that Meeko generates in .meeko/types: the editor completes name="lucide:…", and a name that does not exist is a meeko check error with the closest suggestion. If icon.collections names a collection that is not installed, meeko check, meeko dev and meeko build fail with the command to install it.

Dynamic names

With bind:name, the literals of the expression are resolved at build time just like a fixed name. A value that does not appear in the code, such as one that comes from data, only works if it is in icon.include (or with 'collection:*', which includes the whole collection). Otherwise an empty <svg> is rendered and meeko dev warns in the console.

meeko
<!-- Literals of the expression: they are resolved at compile time -->
<UIIcon
  bind:name="open.value ? 'lucide:x' : 'lucide:menu'"
  bind:label="open.value ? 'Close menu' : 'Open menu'" />

<!-- A value that comes from data: the icon must be in icon.include -->
<UIIcon bind:name="mode.value" />

How it is resolved

  • Literal names. During the build, a Vite plugin resolves each name="…" of <UIIcon> with @iconify/utils and inlines it in the component that uses it, before Meeko compiles it. The bundle of each page only carries its icons; SSR and the browser see the same SVG, so hydration and navigation do not flicker.
  • A name that does not exist. It is a type error (TS2820, with the closest suggestion) in meeko check, meeko build and the editor; the build also rejects it with the file and the position.

Types

The module generates the union of the available names in .meeko/types/modules/%40meekojs%2Ficon/, one declaration per collection plus that of the local SVGs. name of <UIIcon> and icon.include of meeko.config.ts use that union. With Lucide (2,302 names), Tabler and Material Design Icons at once (22,802), TypeScript 7 checking grows by about 35 ms.

ts
import type { IconName } from '@meekojs/icon'

const close: IconName = 'lucide:x'

Your own SVGs

Each file in app/icons (or in the folder of icon.local) is local:<name>, in kebab-case. It is sanitized at build time with a list of allowed elements and attributes: <foreignObject>, <image>, links, animations, class, <script>, <style>, on* handlers, style and every reference outside the icon (href and url(…) only to #id) are removed. Internal ids are prefixed per icon so two icons do not clash, and a single-color icon switches to currentColor.

meeko
<!-- app/icons/paw.svg -->
<UIIcon name="local:paw" label="Meeko" size="1.5em" />

Accessibility, size and color

Without label, the icon is decorative: aria-hidden="true" and focusable="false". With label, the <svg> gets role="img" and aria-label; use it when the icon is the only content of a button or a link.

It is 1em tall by default, so it follows the surrounding text, and it takes its color from currentColor. Width, height, viewBox and fill are attributes, never inline styles: it works with a strict Content Security Policy. The module's stylesheet only aligns the icon with the baseline (.meeko-icon).

API

Props
Name Type / default Description
name IconName Icon: collection:icon or local:name. A literal is resolved at build time.
label string Accessible text. Without it, the icon is decorative.
size string icon.size (1em) Height, a CSS length. The width follows the proportion of the icon.
class string Classes of the svg, which always carries meeko-icon.

Module options

Props
Name Type / default Description
collections string[] [] Iconify collections (lucide, mdi…); each one, the @iconify-json/<collection> package of the project. The editor completes the installed ones.
include IconInclude[] [] Icons for dynamic names, or collection:* for a whole one.
local string | false app/icons if it exists Folder of your own SVGs.
size string 1em Default height of the icons.

Was this page helpful?

Add a comment

It is sent when you choose “Yes” or “No”. Only the page, your answer and the comment are stored: no cookies and no personal data.

0 of 2000 characters