Tessera UI

Components

Icon

A small drawing that stands for an action or a thing, from Lucide. Give it a label when it says something on its own; without one it is decoration and hidden from assistive technology.

The preview needs JavaScript. The code is in the Code tab.

Icon-0.tess
<template>
  <div>
    <Icon name="check" />
    <Icon name="search" size="sm" />
    <Icon name="settings" size="lg" label="Settings" />
    <Button><Icon slot="icon" name="plus" />New</Button>
  </div>
</template>

Installation

npx @pixelscriptio/tessera add Icon
pnpm exec tessera add Icon
yarn tessera add Icon
bunx @pixelscriptio/tessera add Icon
  • Needs the npm package lucide 1.62.0; --install installs it.
  • Works with Tessera 0.9.0 or newer.
  1. Install the dependencies

    npm install lucide@1.62.0
  2. Copy and paste the code into your project

    Put each file in system/components.

    system/components/Icon.tess
    <contract>
    {
      "name": "Icon",
      "role": "img",
      "description": "A small drawing that stands for an action or a thing, from Lucide. Give it a label when it says something on its own; without one it is decoration and hidden from assistive technology.",
      "since": "0.8.0",
      "parts": ["root"],
      "variants": {
        "size": ["md", "sm", "lg"] // first value is the default
      },
      "preview": {
        "props": {
          "name": "settings",
          "label": "Settings"
        }
      }
    }
    </contract>
    
    <script>
    import { ICONS } from './icons.ts'
    
    // `name` picks one of the icons Tessera UI knows; `icon` is any Lucide icon, imported from `lucide`.
    const { name, icon, label } = props({ name: '', icon: null, label: '' })
    
    const shapes = derived(() => icon.value ?? ICONS[name.value] ?? [])
    // Each shape gets an id for `each` to key by: its place in the icon.
    const of = (tag) =>
      shapes.value.flatMap((shape, id) => (shape[0] === tag ? [{ ...shape[1], id }] : []))
    const paths = derived(() => of('path'))
    const circles = derived(() => of('circle'))
    const rects = derived(() => of('rect'))
    const lines = derived(() => of('line'))
    const ellipses = derived(() => of('ellipse'))
    const polylines = derived(() => of('polyline'))
    const polygons = derived(() => of('polygon'))
    </script>
    
    <template>
      <svg
        class="root"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        role="img"
        :aria-label="label || null"
        :aria-hidden="label ? null : 'true'"
        >
        <path each="p in paths" key="p.id" :d="p.d" />
        <circle each="c in circles" key="c.id" :cx="c.cx" :cy="c.cy" :r="c.r" :fill="c.fill ?? null" />
        <rect
          each="r in rects" key="r.id"
          :x="r.x"
          :y="r.y"
          :width="r.width"
          :height="r.height"
          :rx="r.rx ?? null"
          :ry="r.ry ?? null"
          />
        <line each="l in lines" key="l.id" :x1="l.x1" :y1="l.y1" :x2="l.x2" :y2="l.y2" />
        <ellipse each="e in ellipses" key="e.id" :cx="e.cx" :cy="e.cy" :rx="e.rx" :ry="e.ry" />
        <polyline each="p in polylines" key="p.id" :points="p.points" />
        <polygon each="p in polygons" key="p.id" :points="p.points" />
      </svg>
    </template>
    
    <style>
    .root {
      flex: none;
      width: $space.5;
      height: $space.5;
    
      &[data-size='sm'] {
        width: $space.4;
        height: $space.4;
      }
    
      &[data-size='lg'] {
        width: $space.6;
        height: $space.6;
      }
    }
    </style>
    
    system/components/icons.ts
    // The icons the `Icon` component knows by name: a small, common set from Lucide (https://lucide.dev, ISC license).
    // Each is imported by name, so a project that builds with Tessera UI ships these and no others. An icon outside the
    // set is used by importing it from `lucide` and giving it to `Icon` as `icon`.
    import {
      ArrowDown,
      ArrowLeft,
      ArrowRight,
      ArrowUp,
      Bell,
      Bookmark,
      Calendar,
      Check,
      ChevronDown,
      ChevronLeft,
      ChevronRight,
      ChevronUp,
      ChevronsUpDown,
      CircleAlert,
      CircleCheck,
      CircleHelp,
      CircleX,
      Clock,
      Copy,
      Download,
      Ellipsis,
      EllipsisVertical,
      ExternalLink,
      Eye,
      EyeOff,
      File,
      Filter,
      Folder,
      Globe,
      GripVertical,
      Heart,
      House,
      Image,
      Info,
      Link,
      LoaderCircle,
      Lock,
      LogIn,
      LogOut,
      Mail,
      Menu,
      Minus,
      Moon,
      PanelLeft,
      Paperclip,
      Pencil,
      Plus,
      RefreshCw,
      Save,
      Search,
      Send,
      Settings,
      Share,
      SlidersHorizontal,
      Sparkles,
      Star,
      Sun,
      Trash2,
      TriangleAlert,
      Undo2,
      Redo2,
      Upload,
      User,
      Users,
      X,
      Zap,
    } from 'lucide'
    
    /** One shape of an icon: its element name and attributes, as Lucide describes it. */
    export type IconNode = Array<[string, Record<string, string | number>]>
    
    export const ICONS: Record<string, IconNode> = {
      'arrow-down': ArrowDown,
      'arrow-left': ArrowLeft,
      'arrow-right': ArrowRight,
      'arrow-up': ArrowUp,
      bell: Bell,
      bookmark: Bookmark,
      calendar: Calendar,
      check: Check,
      'chevron-down': ChevronDown,
      'chevron-left': ChevronLeft,
      'chevron-right': ChevronRight,
      'chevron-up': ChevronUp,
      'chevrons-up-down': ChevronsUpDown,
      'circle-alert': CircleAlert,
      'circle-check': CircleCheck,
      'circle-help': CircleHelp,
      'circle-x': CircleX,
      clock: Clock,
      copy: Copy,
      download: Download,
      ellipsis: Ellipsis,
      'ellipsis-vertical': EllipsisVertical,
      'external-link': ExternalLink,
      eye: Eye,
      'eye-off': EyeOff,
      file: File,
      filter: Filter,
      folder: Folder,
      globe: Globe,
      'grip-vertical': GripVertical,
      heart: Heart,
      house: House,
      image: Image,
      info: Info,
      link: Link,
      'loader-circle': LoaderCircle,
      lock: Lock,
      'log-in': LogIn,
      'log-out': LogOut,
      mail: Mail,
      menu: Menu,
      minus: Minus,
      moon: Moon,
      'panel-left': PanelLeft,
      paperclip: Paperclip,
      pencil: Pencil,
      plus: Plus,
      redo: Redo2,
      'refresh-cw': RefreshCw,
      save: Save,
      search: Search,
      send: Send,
      settings: Settings,
      share: Share,
      'sliders-horizontal': SlidersHorizontal,
      sparkles: Sparkles,
      star: Star,
      sun: Sun,
      'trash-2': Trash2,
      'triangle-alert': TriangleAlert,
      undo: Undo2,
      upload: Upload,
      user: User,
      users: Users,
      x: X,
      zap: Zap,
    }
    
  3. Check your design tokens

    Your design system needs these. tessera add reports the ones that are missing; by hand, run npx @pixelscriptio/tessera check.

    space.4 space.5 space.6

Usage

Using Icon

A small drawing from Lucide for an action or a thing.

  • name picks one of the icons Tessera UI knows: check, x, plus, search, chevron-down, trash-2, settings and about sixty more. Names are Lucide's, in kebab case.
  • For any other Lucide icon, import it from lucide in your script and pass it as icon. Only the icons you import are in your build.
  • An icon next to text is decoration and says nothing. An icon that stands alone, such as a button with no words, needs a label, which becomes its accessible name. On a Button it is the button that needs the name.
  • size is sm, md or lg. The icon takes the color of the text around it.
  • Put one in a component's icon slot: <Icon slot="icon" name="plus" />.

The preview needs JavaScript. The code is in the Code tab.

Icon-0.tess
<template>
  <div>
    <Icon name="check" />
    <Icon name="search" size="sm" />
    <Icon name="settings" size="lg" label="Settings" />
    <Button><Icon slot="icon" name="plus" />New</Button>
  </div>
</template>

API reference

Variants

VariantValues
sizemd default sm lg

Anatomy

Roleimg
Partsroot
Since0.8.0

Design tokens it uses

Your design system needs these. tessera add checks that it has them, and can add the ones it lacks.

space.4 space.5 space.6

Checksum

8208c784222be71b19e6c6299f4e3de39aab207dd6d353c38e09de309bf316f5