Skip to content

Icons

Klean Icons is a focused family of 98 original SVGs drawn from the actions and objects that repeat across Slipway and Hagfish. Every mark uses the same 24px canvas, calm 1.5px stroke, rounded joins, and optical rhythm.

There is no icon font, runtime package, provider, icon registry in the browser, size prop, color prop, or variant API. Install only the source you use. Then style ordinary SVG with Tailwind or native attributes.

Search is reflected in the page URL, so a filtered catalog can be reloaded, bookmarked, and shared. Framework choice is remembered across the Klean docs.

Installation

Install one icon by its lowercase name. The CLI detects Vue, React, or Svelte and writes the matching source into the conventional icons directory:

Run one command from a Boring Stack application. Klean detects the framework and conventional destination, then adds the framework-native source and its direct dependencies.

Terminal
npx klean-ui add icon trash

  • No initializer or configuration file
  • No framework, alias, or theme questions
  • No Klean runtime dependency

Install several icons in one command when a screen needs a set:

Terminal
npx klean-ui add icon trash search calendar

Re-running add preserves changed local source. Use klean-ui check icon trash, diff icon trash, or update icon trash when you deliberately review upstream changes.

Usage

The component is decorative by default. Put its meaning in visible button or link text, or in the accessible name of the semantic parent control.

DeleteInvoice.vue
<script setup>
import Trash from '@/components/ui/icons/Trash.vue'
</script>

<template>
  <button
    type="button"
    class="inline-flex items-center gap-2 rounded-lg bg-red-600 px-3 py-2 text-sm font-semibold text-white"
    aria-label="Delete invoice"
  >
    <Trash class="size-4" />
    Delete
  </button>
</template>

Styling

Icons use 1em for width and height and currentColor for stroke. Text size and color utilities therefore work without a Klean-specific API:

vue
<Search class="size-5 text-blue-600" />
<Bell class="size-4 text-gray-500" stroke-width="1.75" />
<Rocket class="size-8 text-orange-500" />

Use size-*, text-*, opacity, responsive, state, and dark-mode utilities directly. Native SVG attributes such as stroke-width, aria-label, and role are forwarded by every framework component.

Do not make every icon louder to create hierarchy. Start with one color and one stroke weight. Let placement, visible text, and the parent control carry most of the meaning.

API

InputDefaultPurpose
class / classNameOrdinary Tailwind sizing, color, opacity, state, responsive, and dark-mode utilities.
stroke-width / strokeWidth1.5Native SVG stroke override for a deliberate product treatment.
other SVG attributesnativeIDs, data hooks, event handlers, roles, labels, and other framework-native SVG inputs.
element referenceFramework-native access to the SVG element when genuinely needed.
accessible presentationdecorativeHidden from assistive technology by default; override only for a truly informative mark.

There is no size, color, tone, variant, label, title, spin, filled, or as prop. CSS and SVG already provide those capabilities without another naming layer.

Accessibility

Prefer visible text. In a button that says “Delete”, Trash is decorative and should stay hidden from assistive technology. For an icon-only control, name the button—not the SVG:

vue
<button type="button" aria-label="Delete invoice">
  <Trash class="size-5" />
</button>

Only make the SVG itself informative when it stands alone and no semantic parent can own its name:

vue
<CheckCircle
  class="size-5 text-emerald-600"
  role="img"
  aria-hidden="false"
  aria-label="Payment complete"
/>
  • Never rely on the icon alone when a destructive, irreversible, unfamiliar, or status-changing action needs text.
  • Keep touch and pointer targets on the parent Button or link at least 44px; enlarging the SVG does not enlarge the target.
  • Do not encode status with color alone. Pair the mark with useful text.
  • Avoid redundant names such as a labeled button containing a separately labeled icon.
  • Icons are not focusable. Keyboard focus belongs to the control that owns the action.

Why source-owned icons

An icon is interface source, not an opaque service. Installing the exact Vue, React, or Svelte component means an application can inspect it, change geometry when its product genuinely needs to, and keep rendering without a Klean runtime.

The shared 24px geometry keeps Slipway and Hagfish recognizable as part of the same ecosystem. currentColor, native attributes, and caller-owned classes let each product retain its own density, palette, contrast, and motion without forking an icon library API.

Updates are deliberate. Klean never silently replaces local geometry. The CLI shows whether an installed icon differs, and the application chooses when to inspect and accept a new source version.

Naming

Use the concrete object or action: Trash, Search, Calendar, Copy. Do not encode placement, product, color, or size into the exported name. The same Bell can appear in a header, notification preference, or activity feed without becoming three components.

The CLI uses lowercase kebab-case names while framework exports use PascalCase:

sh
npx klean-ui add icon check-circle chevron-right
js
import CheckCircle from '@/components/ui/icons/CheckCircle.vue'
import ChevronRight from '@/components/ui/icons/ChevronRight.vue'

When to use

Use Klean Icons for repeated application actions, navigation, statuses, dates, identity, infrastructure, notifications, and deployment language. Use the shared source when consistency is more valuable than a product-specific illustration.

When not to use

  • Use text without an icon when the mark adds no recognition or scanning value.
  • Use a logo or product mark for brand identity; do not force a general interface icon to become one.
  • Use an illustration for editorial, empty-state, onboarding, or marketing storytelling.
  • Use Spinner for indeterminate progress and Avatar for resilient identity images.
  • Keep highly specialized domain glyphs in the application until repeated use proves that they belong in the shared family.
  • Button — owns action semantics, target size, pending state, and accessible naming around an icon.
  • Menu, Command, and Tabs — compose icons with truthful actions, destinations, and navigation.
  • Tooltip — adds brief supplementary text to a real icon-only control; it does not replace the control's accessible name.
  • Badge, Alert, and Toast — pair status marks with visible language and truthful announcement behavior.
  • Avatar, Spinner, and Empty State — cover nearby visual roles that should not be folded into the icon contract.

All open source projects are released under the MIT License.