handfish

Handfish Design System

A modern, accessible component library for creative tools. Built with Web Components and CSS Custom Properties.

Demo: https://handfish.noisefactor.io/examples/

Features

Usage

Handfish is served as a versioned ESM bundle from handfish.noisefactor.io. No npm install needed.

Import Styles

<link rel="stylesheet" href="https://handfish.noisefactor.io/0.9.0/styles/tokens.css">
<link rel="stylesheet" href="https://handfish.noisefactor.io/0.9.0/styles/themes/neutral.css">

Or load all styles (tokens + forms + menus + tags):

<link rel="stylesheet" href="https://handfish.noisefactor.io/0.9.0/styles/index.css">

Import Components

Using an import map (recommended):

<script type="importmap">
{
    "imports": {
        "handfish": "https://handfish.noisefactor.io/0.9.0/handfish.esm.min.js"
    }
}
</script>
// Then import by name
import { ToggleSwitch, SliderValue, ColorPicker } from 'handfish'

Or import directly:

import { ToggleSwitch, SliderValue, ColorPicker } from 'https://handfish.noisefactor.io/0.9.0/handfish.esm.min.js'

Use Components

<toggle-switch label="Enable feature"></toggle-switch>

<slider-value min="0" max="100" value="50" step="1" type="int"></slider-value>

<select-dropdown value="option1">
    <option value="option1">Option 1</option>
    <option value="option2">Option 2</option>
</select-dropdown>

<color-picker value="#a5b8ff"></color-picker>

Components

Toggle Switch

A boolean toggle control that replaces <input type="checkbox">.

<toggle-switch
    name="darkMode"
    label="Dark Mode"
    checked
></toggle-switch>
Attribute Type Default Description
checked boolean false Current checked state
label string '' Label text
name string '' Form field name
disabled boolean false Disabled state

Slider Value

A range slider with editable numeric value display. Uses display: contents to participate in parent grid layouts. Click the value to type an exact number.

<slider-value
    name="volume"
    min="0"
    max="100"
    value="50"
    step="1"
    type="int"
></slider-value>
Attribute Type Default Description
min number 0 Minimum value
max number 100 Maximum value
value number 0 Current value
step number 0.01 Step increment
type string 'float' Value type: int or float
name string '' Form field name
disabled boolean false Disabled state

Events: input, change

Select Dropdown

A custom dropdown select with keyboard navigation and search.

<select-dropdown name="effect" value="blur">
    <option value="none">None</option>
    <option value="blur">Blur</option>
    <option value="glow">Glow</option>
</select-dropdown>
Attribute Type Default Description
value string '' Selected option value
name string '' Form field name
disabled boolean false Disabled state

Features:

A trigger button with a dropdown menu for actions.

<dropdown-menu label="Options" icon="more_vert">
    <dropdown-item value="edit" icon="edit">Edit</dropdown-item>
    <dropdown-item value="duplicate" icon="content_copy">Duplicate</dropdown-item>
    <dropdown-item divider></dropdown-item>
    <dropdown-item value="delete" destructive icon="delete">Delete</dropdown-item>
</dropdown-menu>
Attribute Type Default Description
label string '' Trigger button text
icon string '' Material Symbols icon name
align string 'left' Menu alignment: left or right
value string '' Currently selected value (selectable mode)
disabled boolean false Disabled state

<dropdown-item> attributes: value, icon, divider, destructive

Events: change (detail: { value })

Justify Button Group

A segmented control for text alignment selection.

<justify-button-group
    name="alignment"
    value="center"
></justify-button-group>
Attribute Type Default Description
value string 'left' Current value: left, center, right
name string '' Form field name
disabled boolean false Disabled state

Color Picker

A dropdown color picker with swatch trigger.

<color-picker
    name="fillColor"
    value="#a5b8ff"
    alpha="1"
    mode="hsv"
></color-picker>
Attribute Type Default Description
value string '#000000' Hex color value
alpha number 1 Alpha/opacity (0-1)
mode string 'hsv' Color mode: hsv, oklab, or oklch
inline boolean false Always show wheel (no dropdown)
name string '' Form field name
required boolean false Required field
disabled boolean false Disabled state

Events: input, change, colorinput (detail: { value, alpha, rgb, hsv, oklch }), open, close

Color Wheel

The full color wheel interface (used inside Color Picker). Supports three color modes: HSV, OkLab, and OKLCH.

<color-wheel
    value="#6bffa5"
    mode="hsv"
></color-wheel>
Attribute Type Default Description
value string '#000000' Hex color value
alpha number 1 Alpha/opacity (0-1)
mode string 'hsv' Color mode: hsv, oklab, or oklch
name string '' Form field name
required boolean false Required field
disabled boolean false Disabled state

Methods:

Events:

Color Swatch

A single color display with selection and tooltip.

<color-swatch
    color="#a5b8ff"
    size="32"
    selected
    show-tooltip
></color-swatch>
Attribute Type Default Description
color string '#000000' Hex color value
size number 32 Swatch size in pixels
selected boolean false Selected state (shows outline ring)
editable boolean false Enable double-click to edit
show-tooltip boolean false Show hex tooltip on hover
disabled boolean false Disabled state

Events: select (detail: { color }), edit (detail: { color })

Gradient Stops

Draggable color stop handles for positioning colors in a gradient.

<gradient-stops></gradient-stops>
Attribute Type Default Description
disabled boolean false Disabled state

Methods:

Events: select (detail: { index }), input (detail: { index, position, positions }), change (detail: { index, positions }), delete (detail: { index, positions, colors })

Vector 3D Picker

A 3D vector picker with interactive sphere gizmo and XYZ sliders in a dialog modal.

<vector3d-picker
    value="0,1,0"
    min="-1"
    max="1"
    step="0.01"
    normalized
></vector3d-picker>
Attribute Type Default Description
value string '0,0,1' Comma-separated X,Y,Z values
min number -1 Minimum axis value
max number 1 Maximum axis value
step number 0.01 Step increment
normalized boolean false Normalize to unit vector
name string '' Form field name
disabled boolean false Disabled state

Events: input, change

Code Editor

A code editor with line numbers and pluggable syntax highlighting.

<code-editor
    value="// Hello world"
    placeholder="Enter code..."
    line-numbers
></code-editor>
Attribute Type Default Description
value string '' Editor content
placeholder string '' Placeholder text
readonly boolean false Read-only mode
disabled boolean false Disabled state
spellcheck boolean false Enable spell check
line-numbers boolean true Show line numbers
font-family string Override font
font-size string Override font size
background-color string Override background
background-opacity string Override background opacity
text-color string Override text color
caret-color string Override caret color
selection-color string Override selection color

Methods:

Events: input (detail: { value }), forcerecompile

// Use with DSL tokenizer
import { dslTokenizer } from 'handfish'
editor.setTokenizer(dslTokenizer)

Image Magnifier

A zoomed-in view of a canvas under the cursor for precise color picking. Shows crosshairs and the hex value of the center pixel.

<image-magnifier zoom="8" size="120"></image-magnifier>
Attribute Type Default Description
active boolean false Show/hide the magnifier
zoom number 8 Zoom level
size number 120 Magnifier diameter in pixels

Methods:

Toast Notifications

Lightweight notification toasts with auto-dismiss. Exported as standalone functions (not a custom element).

import { showToast, showSuccess, showError, showWarning, showInfo } from 'handfish'

showSuccess('Palette saved')
showError('Failed to load', { duration: 6000 })
showWarning('Unsaved changes')
showInfo('Copied to clipboard')

Utilities

Escape Handler

Stack-based escape key management for closing modals and dropdowns in the correct order.

import { registerEscapeable, unregisterEscapeable, initEscapeHandler } from 'handfish'

initEscapeHandler()
registerEscapeable(element, () => closeMyModal())
unregisterEscapeable(element)

Exports: registerEscapeable, unregisterEscapeable, closeTopmost, hasOpenEscapeables, initEscapeHandler

Tooltips

Hover tooltips for any element with a data-title attribute.

import { initializeTooltips } from 'handfish'

initializeTooltips()
<button data-title="Save palette">Save</button>

Color Conversions

Comprehensive color conversion utilities. All RGB objects use {r, g, b} with 0-255 values.

import { rgbToHex, parseHex, rgbToHsv, hsvToRgb, rgbToOklch, oklchToRgb } from 'handfish'

Design Tokens

All visual values are controlled via CSS custom properties with the --hf- prefix. Colors use OKLCH for perceptually uniform color manipulation. Override any token in your CSS:

:root {
    /* Colors (OKLCH: lightness, chroma, hue) */
    --hf-color-1: oklch(13.9% 0.010 264);
    --hf-accent-3: oklch(79.5% 0.103 264);

    /* Typography */
    --hf-font-family: 'Your Font', sans-serif;

    /* Spacing */
    --hf-space-4: 1rem;
}

Color Tokens

Colors are defined in OKLCH format. Dark mode uses hue 264°, light mode uses hue 90°.

Token Description
--hf-color-1 through --hf-color-7 Base palette (dark to light)
--hf-accent-1 through --hf-accent-4 Accent colors (higher chroma)
--hf-red, --hf-green, --hf-yellow, --hf-blue Semantic colors

Semantic Aliases

These reference the base palette and auto-resolve when the theme changes:

Token Maps to Description
--hf-bg-base --hf-color-1 Page background
--hf-bg-surface --hf-color-2 Card/panel background
--hf-bg-elevated --hf-color-3 Elevated surface (inputs, buttons)
--hf-bg-muted --hf-color-4 Muted/hover background
--hf-text-muted --hf-color-4 Muted text
--hf-text-dim --hf-color-5 Dim/secondary text
--hf-text-normal --hf-color-6 Normal text
--hf-text-bright --hf-color-7 Bright/emphasized text
--hf-border-subtle --hf-color-4 Subtle borders
--hf-accent --hf-accent-3 Primary accent
--hf-accent-hover --hf-accent-4 Accent hover state
--hf-border Semi-transparent accent border
--hf-border-hover Border hover state
--hf-border-focus Border focus state

Typography Tokens

Token Value
--hf-font-family Nunito, system-ui, sans-serif
--hf-font-family-mono ‘Noto Sans Mono’, monospace
--hf-size-xs 0.625rem (10px)
--hf-size-sm 0.75rem (12px)
--hf-size-base 0.875rem (14px)
--hf-size-md 1rem (16px)
--hf-size-lg 1.125rem (18px)
--hf-size-xl 1.25rem (20px)
--hf-size-2xl 1.5rem (24px)
--hf-weight-normal 400
--hf-weight-medium 500
--hf-weight-semibold 600
--hf-weight-bold 700

Spacing Tokens

Token Value
--hf-space-0 0
--hf-space-1 0.25rem (4px)
--hf-space-2 0.5rem (8px)
--hf-space-3 0.75rem (12px)
--hf-space-4 1rem (16px)
--hf-space-5 1.25rem (20px)
--hf-space-6 1.5rem (24px)
--hf-space-8 2rem (32px)
--hf-space-10 2.5rem (40px)
--hf-space-12 3rem (48px)

Border Radius Tokens

Token Value
--hf-radius-sm 0.25rem (4px)
--hf-radius-md 0.375rem (6px)
--hf-radius 0.5rem (8px)
--hf-radius-lg 0.75rem (12px)
--hf-radius-xl 1rem (16px)
--hf-radius-pill 999px
--hf-radius-full 50%

Shadow Tokens

Token Value
--hf-shadow-sm 0 1px 2px rgba(0, 0, 0, 0.1)
--hf-shadow 0 2px 4px rgba(0, 0, 0, 0.15)
--hf-shadow-md 0 4px 8px rgba(0, 0, 0, 0.2)
--hf-shadow-lg 0 8px 16px rgba(0, 0, 0, 0.25)
--hf-shadow-xl 0 16px 32px rgba(0, 0, 0, 0.3)

Control Tokens

Token Value
--hf-control-height-sm 1.5rem (24px)
--hf-control-height 1.875rem (30px)
--hf-control-height-lg 2.25rem (36px)
--hf-control-padding 0.25rem 0.5rem

Transition Tokens

Token Value
--hf-transition-fast 0.1s ease
--hf-transition 0.15s ease
--hf-transition-slow 0.3s ease

Z-Index Scale

Token Value
--hf-z-dropdown 100
--hf-z-sticky 200
--hf-z-fixed 300
--hf-z-modal-backdrop 400
--hf-z-modal 500
--hf-z-popover 600
--hf-z-tooltip 700

Glassmorphism Tokens

Token Value
--hf-glass-blur blur(20px)
--hf-glass-blur-sm blur(8px)
--hf-glass-blur-lg blur(32px)
--hf-backdrop rgba(0, 0, 0, 0.6)
--hf-surface-opacity 92%
--hf-panel-opacity 85%
--hf-header-opacity 65%

Theming

Automatic (System Preference)

Colors automatically adapt to prefers-color-scheme.

Manual Theme

<html data-theme="dark">
<!-- or -->
<html data-theme="light">
// Toggle theme
document.documentElement.dataset.theme =
    document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'

Utility Classes

Typography

<p class="hf-text-sm">Small text</p>
<p class="hf-font-bold">Bold text</p>
<p class="hf-mono">Monospace text</p>
<p class="hf-uppercase">Uppercase</p>
<p class="hf-text-accent">Accent color</p>

Layout

<div class="hf-flex hf-gap-2 hf-items-center">
    <!-- Flexbox with gap -->
</div>

Surfaces

<div class="hf-panel">Glass panel with border</div>
<div class="hf-card">Card with padding</div>

Buttons

<button class="hf-btn">Default</button>
<button class="hf-btn hf-btn-primary">Primary</button>
<button class="hf-btn hf-btn-ghost">Ghost</button>

Browser Support

Requires support for:

Development

# Install dependencies
npm install

# Run examples
npm run dev

# Open http://localhost:3000/examples/

Testing

Visual regression tests use Playwright to screenshot the examples page and compare against baselines:

npm test              # Run visual regression tests
npm run test:update   # Update baseline snapshots after intentional changes

License

MIT