@easemate/web-kit
v0.3.5
Published
UI kit of web components for easemate - a typescript animation library
Downloads
1,702
Maintainers
Readme
A modern, framework-agnostic UI kit of web components for building animation control panels.
Table of Contents
- Features
- Installation
- Quick Start
- React & Next.js
- Components
- Usage Examples
- Configuration
- Theming
- API Reference
- Accessibility
- SSR Support
- Links
- Authors
- License
Features
- Rich Component Library - Sliders, toggles, color pickers, dropdowns, curve editors, and more
- Dark Theme by Default - Beautiful dark UI with OKLAB color palette
- Framework Agnostic - Works with vanilla JS, React, Vue, Svelte, or any framework
- React/Next.js Ready - First-class React integration with hooks and SSR support
- Tree-Shakeable - Import only what you need
- TypeScript First - Full type definitions included
- Accessible - ARIA attributes and keyboard navigation
- Customizable - CSS custom properties and
::partselectors for styling - State Aggregation - Control panel state management with
<ease-state> - Flexible Layout - Separate
<ease-panel>and<ease-state>for maximum flexibility - No CSS Import Required -
initWebKit()handles everything programmatically
Installation
npm install @easemate/web-kit
# or
pnpm add @easemate/web-kit
# or
yarn add @easemate/web-kitQuick Start
Basic Usage
import { initWebKit } from '@easemate/web-kit';
// Minimal - just register components
initWebKit();
// Full setup with theme, styles, and fonts
const kit = initWebKit({
theme: 'default',
styles: 'main',
fonts: 'default'
});
// Components are now registered and ready to use!This single call:
- Registers all custom elements
- Applies the dark theme variables
- Injects CSS reset and base styles
- Loads the default fonts (Instrument Sans, Geist Mono)
Selective Loading
import { initWebKit } from '@easemate/web-kit';
// Only register specific components
initWebKit({
include: ['ease-button', 'ease-slider', 'ease-toggle'],
theme: 'default'
});
// Or exclude components you don't need
initWebKit({
exclude: ['ease-curve', 'ease-code'],
theme: 'default'
});Theme Switching
import { initWebKit, registerTheme } from '@easemate/web-kit';
// Register a custom light theme
registerTheme('light', {
base: null,
config: {
colors: {
gray: { 900: 'oklab(98% 0 0)', 0: 'oklab(20% 0 0)' },
foreground: 'var(--color-gray-0)'
},
vars: {
'--ease-panel-background': 'white',
'--ease-panel-border-color': 'color-mix(in oklab, black 10%, transparent)'
}
}
});
// Initialize with system theme detection
const kit = initWebKit({
theme: {
mode: 'system', // 'light', 'dark', or 'system'
light: 'light',
dark: 'default'
},
styles: 'main',
fonts: 'default'
});
// Switch themes at runtime
kit.theme?.mode('dark');
kit.theme?.set('light');React & Next.js
The library provides first-class React integration via @easemate/web-kit/react.
JSX Types
Importing @easemate/web-kit/react automatically adds JSX types for all ease-* custom elements, including:
- All control components (
ease-slider,ease-toggle,ease-input, etc.) - Layout components (
ease-panel,ease-state,ease-field, etc.) - Advanced components (
ease-curve,ease-code,ease-monitor-fps, etc.) - All icon components (
ease-icon-settings,ease-icon-bezier, etc.)
import '@easemate/web-kit/react';
// Now ease-* elements are typed in JSX
<ease-panel>
<ease-slider name="volume" value={50} />
</ease-panel>You can also import just the JSX types separately (useful for type-only imports):
import '@easemate/web-kit/react/jsx';The types include proper ref types for accessing component methods:
import type { StateElement, PanelElement } from '@easemate/web-kit/react';
const stateRef = useRef<StateElement>(null);
const panelRef = useRef<PanelElement>(null);
// Access typed methods
stateRef.current?.get('volume');
stateRef.current?.reset();
panelRef.current?.setActiveTab(1);Basic Setup
// app/providers.tsx
'use client';
import { useEffect, useState, useRef } from 'react';
import { initWebKit, type WebKitController } from '@easemate/web-kit';
export function Providers({ children }: { children: React.ReactNode }) {
const [ready, setReady] = useState(false);
const controllerRef = useRef<WebKitController | null>(null);
useEffect(() => {
const controller = initWebKit({
theme: 'default',
styles: 'main',
fonts: 'default'
});
controllerRef.current = controller;
controller.ready.then(() => setReady(true));
return () => controller.dispose();
}, []);
return <>{children}</>;
}Next.js App Router
For Next.js 13+ with App Router, create a client component wrapper:
// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}useWebKit Hook
The useWebKit hook initializes the web kit and tracks readiness:
'use client';
import { useState, useEffect, useRef } from 'react';
import { useWebKit } from '@easemate/web-kit/react';
function App() {
const { ready, theme, dispose } = useWebKit(
{
theme: 'default',
styles: 'main',
fonts: 'default',
skip: false // Optional: skip initialization
},
{ useState, useEffect, useRef }
);
if (!ready) return <div>Loading...</div>;
return (
<ease-panel>
<ease-slider name="value" value="0.5" />
</ease-panel>
);
}The hook manages a singleton controller internally, so multiple components using useWebKit will share the same initialization.
useEaseState Hook
The useEaseState hook provides reactive state management for controls:
'use client';
import { useState, useCallback, useRef } from 'react';
import { useEaseState } from '@easemate/web-kit/react';
interface AnimationState {
duration: number;
easing: string;
loop: boolean;
}
function AnimationControls() {
const {
stateRef,
panelRef,
state,
get,
set,
reset,
setTab,
activeTab
} = useEaseState<AnimationState>(
{
initialState: { duration: 1, easing: 'ease-out', loop: false },
onChange: ({ name, value }) => {
console.log(`${name} changed to ${value}`);
},
onTabChange: ({ index }) => {
console.log(`Switched to tab ${index}`);
}
},
{ useState, useCallback, useRef }
);
return (
<ease-panel ref={panelRef} headline="Animation">
<ease-state ref={stateRef}>
<ease-field label="Duration">
<ease-slider name="duration" value="1" min="0" max="5" step="0.1" />
</ease-field>
<ease-field label="Easing">
<ease-dropdown name="easing" value="ease-out">
<button slot="content" value="linear">Linear</button>
<button slot="content" value="ease-out">Ease Out</button>
</ease-dropdown>
</ease-field>
<ease-field label="Loop">
<ease-toggle name="loop" />
</ease-field>
</ease-state>
<div slot="footer">
<ease-button onClick={() => reset()}>Reset</ease-button>
</div>
</ease-panel>
);
}useEaseState Return Values
| Property | Type | Description |
|----------|------|-------------|
| stateRef | (element: EaseStateRef \| null) => void | Ref callback for <ease-state> |
| panelRef | (element: EasePanelRef \| null) => void | Ref callback for <ease-panel> |
| state | T | Current state values (reactive) |
| get | (name: keyof T) => T[keyof T] | Get a specific control value |
| set | (name: keyof T, value: T[keyof T]) => void | Set a control value |
| reset | () => void | Reset all controls to initial values |
| setTab | (index: number) => void | Switch panel tab |
| activeTab | number | Current active tab index |
WebKit Provider
For apps needing shared context, use createWebKitProvider:
// providers.tsx
'use client';
import * as React from 'react';
import { createWebKitProvider } from '@easemate/web-kit/react';
const { WebKitProvider, useWebKitContext } = createWebKitProvider(React);
export { WebKitProvider, useWebKitContext };// layout.tsx
import { WebKitProvider } from './providers';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<WebKitProvider
options={{ theme: 'default', styles: 'main', fonts: 'default' }}
immediate={true} // Render children before ready (default: true)
>
{children}
</WebKitProvider>
);
}// component.tsx
import { useWebKitContext } from './providers';
function MyComponent() {
const { ready, theme } = useWebKitContext();
if (!ready) return <div>Loading...</div>;
return <ease-slider name="value" value="0.5" />;
}Event Utilities
The React module exports typed event creators:
import { createEventHandler, type ControlChangeEvent, type StateChangeEvent, type TabChangeEvent } from '@easemate/web-kit/react';
// Create typed event handlers
const handleChange = createEventHandler<ControlChangeEvent>((e) => {
console.log(e.detail.name, e.detail.value);
});Components
Controls
| Component | Tag | Description |
|-----------|-----|-------------|
| Slider | <ease-slider> | Range slider with min/max/step |
| Toggle | <ease-toggle> | Boolean switch |
| Checkbox | <ease-checkbox> | Checkbox input |
| Input | <ease-input> | Text input |
| NumberInput | <ease-number-input> | Numeric input with stepper |
| ColorInput | <ease-color-input> | Color input with picker |
| ColorPicker | <ease-color-picker> | Full color picker UI |
| Dropdown | <ease-dropdown> | Select dropdown |
| RadioGroup | <ease-radio-group> | Radio button group |
| RadioInput | <ease-radio-input> | Individual radio option |
| Origin | <ease-origin> | Transform origin picker |
Layout & Display
| Component | Tag | Description |
|-----------|-----|-------------|
| Panel | <ease-panel> | Visual container with tabs, header, and footer |
| Folder | <ease-folder> | Collapsible container for grouping controls |
| State | <ease-state> | State aggregator for controls (no visual styling) |
| Field | <ease-field> | Label + control wrapper |
| Button | <ease-button> | Action button |
| Tooltip | <ease-tooltip> | Tooltip wrapper |
| Popover | <ease-popover> | Floating content |
Advanced
| Component | Tag | Description |
|-----------|-----|-------------|
| Curve | <ease-curve> | Cubic bezier / linear easing editor |
| Code | <ease-code> | Syntax highlighted code |
| Monitor | <ease-monitor> | Value monitor display |
| MonitorFps | <ease-monitor-fps> | FPS counter |
| LogoLoader | <ease-logo-loader> | Animated logo with intro animations and loading state |
Icons
All icon components follow the pattern <ease-icon-*>. All icons are typed in JSX when importing @easemate/web-kit/react.
Interface Icons:
| Tag | Description |
|-----|-------------|
| ease-icon-settings | Settings gear icon |
| ease-icon-dots | Three dots / more menu icon |
| ease-icon-plus | Plus / add icon |
| ease-icon-minus | Minus / remove icon |
| ease-icon-check | Checkmark icon |
| ease-icon-code | Code brackets icon |
| ease-icon-picker | Eyedropper picker icon |
| ease-icon-mention | @ mention icon |
| ease-icon-arrow-up | Up arrow icon |
| ease-icon-arrows-vertical | Vertical arrows icon |
| ease-icon-circle-arrow-left | Left arrow in circle |
| ease-icon-circle-arrow-right | Right arrow in circle |
| ease-icon-anchor-add | Add anchor point |
| ease-icon-anchor-remove | Remove anchor point |
| ease-icon-bezier | Bezier curve icon |
| ease-icon-bezier-angle | Bezier angle tool |
| ease-icon-bezier-distribute | Distribute bezier points |
| ease-icon-bezier-length | Bezier length tool |
| ease-icon-bezier-mirror | Mirror bezier handles |
Animation Icons:
| Tag | Description |
|-----|-------------|
| ease-icon-chevron | Animated chevron (expands/collapses) |
| ease-icon-clear | Animated clear/X icon |
| ease-icon-folder | Animated folder open/close icon |
| ease-icon-grid | Animated grid icon |
| ease-icon-loading | Animated loading spinner |
| ease-icon-snap | Animated snap indicator |
Usage Examples
Basic Controls
<ease-slider name="opacity" value="0.5" min="0" max="1" step="0.01"></ease-slider>
<ease-toggle name="visible" checked></ease-toggle>
<ease-color-input name="background" value="#3b82f6"></ease-color-input>
<ease-input name="label" value="Hello"></ease-input>
<ease-number-input name="count" value="42" min="0" max="100"></ease-number-input>Panel Component
The <ease-panel> component provides the visual container with optional tabs, header actions, and footer. It does NOT manage state - use <ease-state> for that.
<!-- Simple panel with headline -->
<ease-panel headline="Settings">
<div>
<!-- Your content here -->
</div>
</ease-panel>
<!-- Panel with tabs -->
<ease-panel headline="Animation" active-tab="0">
<div slot="tab-general" data-tab-label="General">
<!-- General settings -->
</div>
<div slot="tab-advanced" data-tab-label="Advanced">
<!-- Advanced settings -->
</div>
</ease-panel>
<!-- Panel with header actions -->
<ease-panel headline="Controls">
<button slot="actions" title="Settings">
<ease-icon-settings></ease-icon-settings>
</button>
<div>
<!-- Content -->
</div>
<div slot="footer">
<ease-button>Save</ease-button>
</div>
</ease-panel>
<!-- Panel with max-height (scrollable) -->
<ease-panel headline="Scrollable Panel" max-height="250px">
<!-- Content will scroll when it exceeds 250px -->
</ease-panel>Folder Component
The <ease-folder> component provides a collapsible container for grouping controls. Click the header to toggle open/closed.
<!-- Basic folder (closed by default) -->
<ease-folder headline="Transform">
<ease-field label="X">
<ease-number-input name="x" value="0"></ease-number-input>
</ease-field>
<ease-field label="Y">
<ease-number-input name="y" value="0"></ease-number-input>
</ease-field>
</ease-folder>
<!-- Open folder -->
<ease-folder open headline="Appearance">
<ease-field label="Color">
<ease-color-input name="color" value="#3b82f6"></ease-color-input>
</ease-field>
</ease-folder>
<!-- Folder with max-height (scrollable with fade masks) -->
<ease-folder headline="Animation" max-height="150px">
<!-- Many fields here will scroll with fade effect -->
</ease-folder>
State Component
The <ease-state> component aggregates state from child controls. It can be used standalone (no panel styling) or inside a panel.
<!-- Standalone state (no panel) - useful for embedding in your own UI -->
<ease-state>
<ease-field label="Duration">
<ease-slider name="duration" value="1" min="0" max="5" step="0.1"></ease-slider>
</ease-field>
<ease-field label="Easing">
<ease-dropdown name="easing" value="ease-out">
<button slot="content" value="linear">Linear</button>
<button slot="content" value="ease-out">Ease Out</button>
</ease-dropdown>
</ease-field>
<ease-field label="Loop">
<ease-toggle name="loop"></ease-toggle>
</ease-field>
</ease-state>Combined Panel + State
For a complete control panel experience, combine <ease-panel> with <ease-state>:
<ease-panel headline="Animation Controls">
<button slot="actions" title="Reset">
<ease-icon-minus></ease-icon-minus>
</button>
<ease-state>
<ease-field label="Duration">
<ease-slider name="duration" value="1" min="0" max="5" step="0.1"></ease-slider>
</ease-field>
<ease-field label="Easing">
<ease-dropdown name="easing" value="ease-out">
<button slot="content" value="linear">Linear</button>
<button slot="content" value="ease-in">Ease In</button>
<button slot="content" value="ease-out">Ease Out</button>
<button slot="content" value="ease-in-out">Ease In-Out</button>
</ease-dropdown>
</ease-field>
<ease-field label="Loop">
<ease-toggle name="loop"></ease-toggle>
</ease-field>
</ease-state>
<div slot="footer">
<ease-button>Apply</ease-button>
<ease-button variant="secondary">Cancel</ease-button>
</div>
</ease-panel>Panel with Tabs + State
When using tabs with state, place the <ease-state> inside each tab:
<ease-panel headline="Animation" active-tab="0">
<button slot="actions" title="Reset">
<ease-icon-minus></ease-icon-minus>
</button>
<div slot="tab-transform" data-tab-label="Transform">
<ease-state>
<ease-field label="X">
<ease-number-input name="x" value="0"></ease-number-input>
</ease-field>
<ease-field label="Y">
<ease-number-input name="y" value="0"></ease-number-input>
</ease-field>
<ease-field label="Rotation">
<ease-slider name="rotation" value="0" min="0" max="360"></ease-slider>
</ease-field>
</ease-state>
</div>
<div slot="tab-style" data-tab-label="Style">
<ease-state>
<ease-field label="Opacity">
<ease-slider name="opacity" value="1" min="0" max="1" step="0.01"></ease-slider>
</ease-field>
<ease-field label="Color">
<ease-color-input name="color" value="#3b82f6"></ease-color-input>
</ease-field>
</ease-state>
</div>
</ease-panel>JavaScript Integration
// Working with ease-state
const state = document.querySelector('ease-state');
// Get current state
console.log(state.state); // { duration: 1, easing: 'ease-out', loop: false }
// Get individual value
const duration = state.get('duration');
// Set value programmatically
state.set('duration', 2.5);
// Subscribe to changes
const sub = state.subscribe((value, name) => {
console.log(`${name} changed to:`, value);
});
// Subscribe to specific control
state.subscribe('duration', (value) => {
myAnimation.duration = value;
});
// Reset to initial values
state.reset();
// Cleanup
sub.unsubscribe();// Working with ease-panel
const panel = document.querySelector('ease-panel');
// Get current active tab index
console.log(panel.activeTab); // 0
// Switch to a specific tab programmatically
panel.setTab(1); // Switch to second tab (0-indexed)
// Or set directly via property
panel.activeTab = 2;Logo Loader
The <ease-logo-loader> component displays an animated logo with intro animations and an optional loading state.
<!-- Basic usage - plays wave intro on load -->
<ease-logo-loader></ease-logo-loader>
<!-- With particle intro animation -->
<ease-logo-loader intro="particle"></ease-logo-loader>
<!-- With loading state -->
<ease-logo-loader loading></ease-logo-loader>
<!-- Custom size -->
<ease-logo-loader size="48"></ease-logo-loader>Logo Loader Attributes
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| intro | 'wave' \| 'particle' | 'wave' | Intro animation variant played on mount |
| loading | boolean | false | When true, plays continuous loading animation |
| size | number | 36 | Size in pixels |
| aria-label | string | - | Accessible label for the logo |
Intro Animations
- Wave (default): Inner dots appear at half scale, then expand while outer dots fade in with a staggered wave effect
- Particle: Dots fly in from outside with curved bezier paths, rotation, and shockwave effects on impact
Loading Animation
When the loading attribute is set:
- Inner dots scale down and pulse with a breathing effect
- Outer dots animate in a circular wave pattern
- Animation completes its current cycle before stopping when
loadingis removed
JavaScript API
const logo = document.querySelector('ease-logo-loader');
// Toggle loading state
logo.loading = true;
logo.loading = false;
// Replay intro animation
logo.playIntro(); // Uses current intro variant
logo.playIntro('wave'); // Force wave intro
logo.playIntro('particle'); // Force particle introTheming
The logo uses theme color tokens:
| CSS Variable | Default | Description |
|--------------|---------|-------------|
| --dot-dark | var(--color-gray-0) | Brightest dot color (inner dots) |
| --dot-medium | var(--color-gray-600) | Medium dot color |
| --dot-light | var(--color-gray-700) | Dimmest dot color (outer dots) |
| --dot-accent | var(--color-blue-600) | Accent color for effects |
Event Handling
All controls dispatch standard events:
// Standard control-change event
slider.addEventListener('control-change', (e: CustomEvent) => {
const { name, value, event } = e.detail;
console.log(`${name}: ${value}`);
});
// State aggregator events
state.addEventListener('state-change', (e: CustomEvent) => {
const { name, value, state } = e.detail;
console.log('Full state:', state);
});
// Panel tab change event
panel.addEventListener('tab-change', (e: CustomEvent) => {
const { index, id, event } = e.detail;
console.log(`Switched to tab ${id} (index: ${index})`);
});Event Types
| Event | Component | Detail | Description |
|-------|-----------|--------|-------------|
| control-change | Controls | { name, value, event } | Fired when value changes |
| state-change | <ease-state> | { name, value, state, event } | Fired when any control changes |
| tab-change | <ease-panel> | { index, id, event } | Fired when active tab changes |
Configuration
initWebKit Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| include | string[] | - | Only register these component tags |
| exclude | string[] | - | Register all except these tags |
| replace | Record<string, Constructor \| string> | - | Replace components with custom implementations |
| theme | string \| ThemeRef \| ThemeConfig \| ThemeModeConfig | - | Theme to apply |
| target | HTMLElement | document.documentElement | Element to scope theme vars to |
| styles | false \| 'reset' \| 'base' \| 'main' | false | Inject global styles |
| fonts | false \| 'default' \| FontConfig | false | Font loading configuration |
| lazyLoad | boolean \| LazyLoadConfig | false | Enable lazy component loading |
| cspNonce | string | - | CSP nonce for injected elements |
| dev | { warnUnknownTags?: boolean; logLoads?: boolean } | - | Development options |
Theme Configuration
// Theme mode configuration for light/dark switching
interface ThemeModeConfig {
mode: 'light' | 'dark' | 'system';
light: ThemeInput;
dark: ThemeInput;
persist?: { key: string }; // Coming soon
}Custom Theme Registration
Register custom themes using registerTheme(). No TypeScript declaration files needed - custom theme names are fully supported:
import { initWebKit, registerTheme } from '@easemate/web-kit';
// Register a custom theme - returns a typed theme ref
const brandTheme = registerTheme('brand', {
base: 'default', // Inherit from built-in theme ('default' or 'dark')
config: {
typography: {
fontFamily: '"Inter", system-ui, sans-serif'
},
vars: {
'--ease-panel-radius': '16px',
'--ease-panel-padding': '16px'
}
}
});
// Use the theme ref (type-safe)
initWebKit({ theme: brandTheme });
// Or use the string name directly (also works!)
initWebKit({ theme: 'brand' });Theme Inheritance
Themes can extend other themes using the base option:
// Create a base brand theme
const brandBase = registerTheme('brand-base', {
base: 'default',
config: {
typography: { fontFamily: '"Inter", sans-serif' },
vars: { '--ease-panel-radius': '16px' }
}
});
// Create variants that extend brand-base
const brandLight = registerTheme('brand-light', {
base: brandBase, // Can use theme ref or string name
config: {
colors: {
gray: { 900: 'oklab(98% 0 0)', 0: 'oklab(20% 0 0)' }
},
vars: { '--ease-panel-background': 'white' }
}
});
const brandDark = registerTheme('brand-dark', {
base: 'brand-base', // String name works too
config: {
vars: { '--ease-panel-background': 'var(--color-gray-1000)' }
}
});
// Use with theme mode switching
initWebKit({
theme: {
mode: 'system',
light: brandLight,
dark: brandDark
}
});Creating a Theme from Scratch
Use base: null to create a theme without inheriting defaults:
const minimalTheme = registerTheme('minimal', {
base: null, // Start fresh, no inheritance
config: {
colors: {
gray: { 900: '#f5f5f5', 0: '#171717' },
blue: { 500: '#3b82f6' }
},
vars: {
'--ease-panel-background': 'white',
'--ease-panel-border-color': '#e5e5e5'
}
}
});Theme Utilities
import {
registerTheme,
getTheme,
hasTheme,
getThemeNames,
themeRef
} from '@easemate/web-kit';
// Check if a theme exists
if (hasTheme('brand')) {
console.log('Brand theme is registered');
}
// Get all registered theme names
const themes = getThemeNames(); // ['default', 'dark', 'brand', ...]
// Get resolved theme config (with inheritance applied)
const config = getTheme('brand');
// Get a theme ref for an already-registered theme
const ref = themeRef('brand');Font Configuration
// Use default fonts (Instrument Sans, Geist Mono)
fonts: 'default'
// Custom Google fonts
fonts: {
'Inter': { source: 'google', family: 'Inter', css2: '[email protected]' },
'JetBrains Mono': { source: 'google', family: 'JetBrains Mono' }
}
// Self-hosted fonts
fonts: {
'Custom Font': { source: 'css', url: '/fonts/custom.css' }
}Lazy Loading
initWebKit({
lazyLoad: true, // Auto-define components when they appear in DOM
theme: 'default'
});
// Advanced lazy loading
initWebKit({
lazyLoad: {
strategy: 'mutation',
include: ['ease-slider', 'ease-toggle'],
preload: ['ease-button'] // Load immediately
}
});Component Replacement
import { initWebKit } from '@easemate/web-kit';
import { CustomInput } from './custom-input';
initWebKit({
replace: {
'ease-input': CustomInput, // Your custom element class
// or alias to another tag:
// 'ease-input': 'my-custom-input'
},
theme: 'default'
});Theming
CSS Custom Properties
All components use CSS custom properties. Override them at any scope:
:root {
--ease-panel-padding: 16px;
--ease-panel-radius: 14px;
--ease-button-radius: 8px;
}
/* Or scope to specific containers */
.my-panel {
--ease-panel-background: var(--my-bg);
}Multiple themes using data-ease-theme:
:root[data-ease-theme='dark'] {
--ease-panel-background: var(--color-gray-1000);
}
:root[data-ease-theme='light'] {
--ease-panel-background: white;
}JavaScript Theme API
import { applyTheme, createTheme, mergeTheme, setThemeName } from '@easemate/web-kit/theme';
// Merge with defaults
const theme = mergeTheme({
vars: { '--ease-panel-padding': '16px' }
});
// Apply to document
applyTheme(theme, { name: 'custom', colorScheme: 'dark' });
// Generate CSS string
const css = createTheme(theme, ':root[data-ease-theme="custom"]');
// Switch theme name
setThemeName('light', { colorScheme: 'light' });Token Reference
Global Tokens
| Category | Variables |
|----------|-----------|
| Colors | --color-gray-*, --color-blue-*, --color-green-*, --color-red-*, --color-orange-*, --color-yellow-* |
| Radii | --radii-sm, --radii-md, --radii-lg, --radii-xl, --radii-full |
| Spacing | --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, --spacing-xl |
| Typography | --font-family, --font-mono, --font-size, --font-line-height |
UI Kit Tokens (--ease-*)
| Category | Variables |
|----------|-----------|
| Typography | --ease-font-family, --ease-font-mono, --ease-font-size, --ease-line-height |
| Panel | --ease-panel-max-width, --ease-panel-padding, --ease-panel-radius, --ease-panel-background, --ease-panel-border-color, --ease-panel-shadow, --ease-panel-gap, --ease-panel-header-spacing, --ease-panel-fade-size |
| Panel Title | --ease-panel-title-font-size, --ease-panel-title-font-weight, --ease-panel-title-color |
| Panel Tabs | --ease-panel-tab-font-size, --ease-panel-tab-font-weight, --ease-panel-tab-color, --ease-panel-tab-color-hover, --ease-panel-tab-color-active, --ease-panel-tab-background-active, --ease-panel-tab-radius |
| Panel Actions | --ease-panel-action-icon-size |
| Panel Footer | --ease-panel-footer-padding |
| Folder | --ease-folder-padding, --ease-folder-radius, --ease-folder-border-color, --ease-folder-background, --ease-folder-shadow, --ease-folder-gap, --ease-folder-title-font-size, --ease-folder-title-font-weight, --ease-folder-title-color, --ease-folder-icon-color, --ease-folder-chevron-color, --ease-folder-chevron-color-hover, --ease-folder-fade-size |
| Field | --ease-field-label-width, --ease-field-column-gap, --ease-field-row-gap |
| Controls | Each control exposes --ease-<component>-* tokens |
API Reference
Controller API
initWebKit() returns a controller object:
interface WebKitController {
dispose: () => void; // Cleanup all injected resources
ready: Promise<void>; // Resolves when components are loaded
theme?: {
set: (theme) => void; // Set theme by name/ref/config
mode?: (mode) => void; // Set mode (light/dark/system)
};
}Package Exports
| Export | Description |
|--------|-------------|
| @easemate/web-kit | Main entry: initWebKit(), theme utilities, and all types |
| @easemate/web-kit/react | React hooks (useWebKit, useEaseState), provider, event utilities, and JSX types |
| @easemate/web-kit/react/jsx | JSX type augmentation only (for TypeScript) |
| @easemate/web-kit/register | Side-effect import that registers all components (SSR-safe) |
| @easemate/web-kit/elements | Individual element classes (Button, Slider, Panel, etc.) |
| @easemate/web-kit/decorators | @Component, @Prop, @Watch, @Listen, @Query decorators |
| @easemate/web-kit/theme | Theme API: applyTheme, createTheme, mergeTheme, registerTheme |
| @easemate/web-kit/utils | Template helpers: classMap, styleMap, when, repeat, etc. |
Panel API
The <ease-panel> component provides the visual container.
Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| headline | string \| null | null | Panel title text displayed in the header |
| activeTab | number | 0 | Zero-based index of the active tab |
| maxHeight | string \| null | null | Maximum height for scrollable content (e.g., "250px") |
Methods
| Method | Signature | Description |
|--------|-----------|-------------|
| setTab | (index: number) => void | Switch to a specific tab by index |
Slots
| Slot | Description |
|------|-------------|
| actions | Header action buttons, links, or dropdowns |
| (default) | Main content area (used when no tabs) |
| tab-{id} | Tab panel content (use data-tab-label for display name) |
| footer | Footer content below all panels |
CSS Parts
| Part | Description |
|------|-------------|
| section | Outer container |
| header | Header row containing headline/tabs and actions |
| headline | Title element |
| tabs | Tab button container |
| tab | Individual tab button |
| actions | Actions container |
| content | Content wrapper (handles height animations) |
| body | Inner body container (scrollable when max-height is set) |
| items | Grid container for slotted content |
| tab-panel | Individual tab panel |
| footer | Footer container |
Events
| Event | Detail Type | Description |
|-------|-------------|-------------|
| tab-change | TabChangeEventDetail | Fired when the active tab changes |
interface TabChangeEventDetail {
index: number; // Tab index (0-based)
id: string; // Tab id from slot name
event: Event; // Original event
}Folder API
The <ease-folder> component provides a collapsible container for grouping controls.
Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| headline | string \| null | null | Folder title text displayed in the header |
| open | boolean | false | Whether the folder is expanded |
| maxHeight | string \| null | null | Maximum height for scrollable content (e.g., "150px") |
Methods
| Method | Signature | Description |
|--------|-----------|-------------|
| toggle | () => void | Toggle the folder open/closed state |
Slots
| Slot | Description | |------|-------------| | (default) | Folder content (fields and controls) |
CSS Parts
| Part | Description |
|------|-------------|
| section | Outer container |
| header | Clickable header row |
| icon | Folder icon |
| headline | Title element |
| chevron | Chevron icon (toggle indicator) |
| content | Content wrapper (handles height animations) |
| body | Inner body container (scrollable when max-height is set) |
Events
| Event | Detail Type | Description |
|-------|-------------|-------------|
| folder-toggle | FolderToggleEventDetail | Fired when the folder is opened or closed |
interface FolderToggleEventDetail {
open: boolean; // Whether the folder is now open
event: Event; // Original event
}Scrollable Content
When max-height is set, the folder content becomes scrollable with fade gradient masks at the top and bottom edges. The masks automatically appear/disappear based on scroll position using scroll-driven animations.
State API
The <ease-state> component provides state management for controls.
Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| value | string \| null | null | Reflects the last changed control's value |
| state | Record<string, unknown> | {} | Read-only object containing all control values |
Methods
| Method | Signature | Description |
|--------|-----------|-------------|
| get | (name: string) => unknown | Get a specific control value by name |
| set | (name: string, value: unknown) => void | Set a control value programmatically |
| subscribe | (callback: (value, name) => void) => { unsubscribe } | Subscribe to all state changes |
| subscribe | (name: string, callback: (value, name) => void) => { unsubscribe } | Subscribe to a specific control |
| reset | () => void | Reset all controls to their initial values |
Slots
| Slot | Description | |------|-------------| | (default) | Controls to aggregate state from |
CSS Parts
| Part | Description |
|------|-------------|
| container | Inner container wrapping controls |
Events
| Event | Detail Type | Description |
|-------|-------------|-------------|
| state-change | StateChangeEventDetail | Fired when any control value changes |
interface StateChangeEventDetail {
name: string; // Control name
value: unknown; // New value
state: Record<string, unknown>; // Complete state object
event: Event; // Original event
}Accessibility
Components include:
- ARIA attributes (
role,aria-*) - Keyboard navigation (Tab, Arrow keys, Enter, Escape)
- Focus management
- Screen reader support
disabledstate handling
SSR Support
The package is SSR-safe. initWebKit() is a no-op in server environments:
import { initWebKit } from '@easemate/web-kit';
// Safe on server - returns immediately without side effects
const kit = initWebKit({ theme: 'default' });
await kit.ready; // Resolves immediately on serverFor React/Next.js, all hooks check for browser environment:
// This is safe to call on the server
const { ready, theme } = useWebKitContext();
// ready will be false on server, true after hydrationLinks
Authors
License
MIT © Aaron Iker
