UI kit of web components for easemate - a typescript animation library
npm install @easemate/web-kit
A modern, framework-agnostic UI kit of web components for building animation control panels.

!npm package minimized gzipped size


- Features
- Installation
- Quick Start
- Basic Usage
- Selective Loading
- Theme Switching
- React & Next.js
- JSX Types
- Basic Setup
- Next.js App Router
- useWebKit Hook
- useEaseState Hook
- WebKit Provider
- Event Utilities
- Components
- Controls
- Layout & Display
- Advanced
- Icons
- Usage Examples
- Basic Controls
- Panel Component
- Folder Component
- State Component
- Combined Panel + State
- JavaScript Integration
- Event Handling
- Configuration
- initWebKit Options
- Theme Configuration
- Custom Theme Registration
- Theme Inheritance
- Creating a Theme from Scratch
- Theme Utilities
- Font Configuration
- Lazy Loading
- Component Replacement
- Theming
- CSS Custom Properties
- JavaScript Theme API
- Token Reference
- API Reference
- Controller API
- Package Exports
- Panel API
- Folder API
- State API
- Accessibility
- SSR Support
- Links
- Authors
- License
---
- 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 ::part selectors for styling
- State Aggregation - Control panel state management with
- Flexible Layout - Separate and for maximum flexibility
- No CSS Import Required - initWebKit() handles everything programmatically
---
``bash`
npm install @easemate/web-kitor
pnpm add @easemate/web-kitor
yarn add @easemate/web-kit
---
`typescript
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)
`typescript
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'
});
`
`typescript
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');
`
---
The library provides first-class React integration via @easemate/web-kit/react.
Importing @easemate/web-kit/react automatically adds JSX types for all ease-* custom elements, including:ease-slider
- All control components (, ease-toggle, ease-input, etc.)ease-panel
- Layout components (, ease-state, ease-field, etc.)ease-curve
- Advanced components (, ease-code, ease-monitor-fps, etc.)ease-icon-settings
- All icon components (, ease-icon-bezier, etc.)
`tsx
import '@easemate/web-kit/react';
// Now ease-* elements are typed in JSX
`
You can also import just the JSX types separately (useful for type-only imports):
`tsx`
import '@easemate/web-kit/react/jsx';
The types include proper ref types for accessing component methods:
`tsx
import type { StateElement, PanelElement } from '@easemate/web-kit/react';
const stateRef = useRef
const panelRef = useRef
// Access typed methods
stateRef.current?.get('volume');
stateRef.current?.reset();
panelRef.current?.setActiveTab(1);
`
`tsx
// 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
useEffect(() => {
const controller = initWebKit({
theme: 'default',
styles: 'main',
fonts: 'default'
});
controllerRef.current = controller;
controller.ready.then(() => setReady(true));
return () => controller.dispose();
}, []);
return <>{children}>;
}
`
For Next.js 13+ with App Router, create a client component wrapper:
`tsx
// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
$3
The
useWebKit hook initializes the web kit and tracks readiness:`tsx
'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
Loading...; return (
);
}
`The hook manages a singleton controller internally, so multiple components using
useWebKit will share the same initialization.$3
The
useEaseState hook provides reactive state management for controls:`tsx
'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(
{
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 (
reset()}>Reset
);
}
`#### useEaseState Return Values
| Property | Type | Description |
|----------|------|-------------|
|
stateRef | (element: EaseStateRef \| null) => void | Ref callback for |
| panelRef | (element: EasePanelRef \| null) => void | Ref callback for |
| 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 |$3
For apps needing shared context, use
createWebKitProvider:`tsx
// providers.tsx
'use client';import * as React from 'react';
import { createWebKitProvider } from '@easemate/web-kit/react';
const { WebKitProvider, useWebKitContext } = createWebKitProvider(React);
export { WebKitProvider, useWebKitContext };
``tsx
// layout.tsx
import { WebKitProvider } from './providers';export default function Layout({ children }: { children: React.ReactNode }) {
return (
options={{ theme: 'default', styles: 'main', fonts: 'default' }}
immediate={true} // Render children before ready (default: true)
>
{children}
);
}
``tsx
// component.tsx
import { useWebKitContext } from './providers';function MyComponent() {
const { ready, theme } = useWebKitContext();
if (!ready) return
Loading...; return ;
}
`$3
The React module exports typed event creators:
`tsx
import { createEventHandler, type ControlChangeEvent, type StateChangeEvent, type TabChangeEvent } from '@easemate/web-kit/react';// Create typed event handlers
const handleChange = createEventHandler((e) => {
console.log(e.detail.name, e.detail.value);
});
`---
Components
$3
| Component | Tag | Description |
|-----------|-----|-------------|
| Slider |
| Range slider with min/max/step |
| Toggle | | Boolean switch |
| Checkbox | | Checkbox input |
| Input | | Text input |
| NumberInput | | Numeric input with stepper |
| ColorInput | | Color input with picker |
| ColorPicker | | Full color picker UI |
| Dropdown | | Select dropdown |
| RadioGroup | | Radio button group |
| RadioInput | | Individual radio option |
| Origin | | Transform origin picker |$3
| Component | Tag | Description |
|-----------|-----|-------------|
| Panel |
| Visual container with tabs, header, and footer |
| Folder | | Collapsible container for grouping controls |
| State | | State aggregator for controls (no visual styling) |
| Field | | Label + control wrapper |
| Button | | Action button |
| Tooltip | | Tooltip wrapper |
| Popover | | Floating content |$3
| Component | Tag | Description |
|-----------|-----|-------------|
| Curve |
| Cubic bezier / linear easing editor |
| Code | | Syntax highlighted code |
| Monitor | | Value monitor display |
| MonitorFps | | FPS counter |
| LogoLoader | | Animated logo with intro animations and loading state |$3
All icon components follow the pattern
. 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
$3
`html
`$3
The
component provides the visual container with optional tabs, header actions, and footer. It does NOT manage state - use for that.`html
Save
`$3
The
component provides a collapsible container for grouping controls. Click the header to toggle open/closed.`html
`$3
The
component aggregates state from child controls. It can be used standalone (no panel styling) or inside a panel.`html
`$3
For a complete control panel experience, combine
with :`html
Apply
Cancel
`#### Panel with Tabs + State
When using tabs with state, place the
inside each tab:`html
`$3
`typescript
// 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();
``typescript
// 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;
`$3
The
component displays an animated logo with intro animations and an optional loading state.`html
`#### 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:
1. Inner dots scale down and pulse with a breathing effect
2. Outer dots animate in a circular wave pattern
3. Animation completes its current cycle before stopping when loading is removed#### JavaScript API
`typescript
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 intro
`#### Theming
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 |$3
All controls dispatch standard events:
`typescript
// 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 | | { name, value, state, event } | Fired when any control changes |
| tab-change | | { index, id, event } | Fired when active tab changes |---
Configuration
$3
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
include | string[] | - | Only register these component tags |
| exclude | string[] | - | Register all except these tags |
| replace | Record | - | 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 |$3
`typescript
// 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:`typescript
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:`typescript
// 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:`typescript
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
`typescript
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');
`$3
`typescript
// Use default fonts (Instrument Sans, Geist Mono)
fonts: 'default'// Custom Google fonts
fonts: {
'Inter': { source: 'google', family: 'Inter', css2: 'wght@400..700' },
'JetBrains Mono': { source: 'google', family: 'JetBrains Mono' }
}
// Self-hosted fonts
fonts: {
'Custom Font': { source: 'css', url: '/fonts/custom.css' }
}
`$3
`typescript
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
}
});
`$3
`typescript
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
$3
All components use CSS custom properties. Override them at any scope:
`css
: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:`css
:root[data-ease-theme='dark'] {
--ease-panel-background: var(--color-gray-1000);
}:root[data-ease-theme='light'] {
--ease-panel-background: white;
}
`$3
`typescript
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' });
`$3
#### 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- tokens |---
API Reference
$3
initWebKit() returns a controller object:`typescript
interface WebKitController {
dispose: () => void; // Cleanup all injected resources
ready: Promise; // Resolves when components are loaded
theme?: {
set: (theme) => void; // Set theme by name/ref/config
mode?: (mode) => void; // Set mode (light/dark/system)
};
}
`$3
| 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. |$3
The
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 |`typescript
interface TabChangeEventDetail {
index: number; // Tab index (0-based)
id: string; // Tab id from slot name
event: Event; // Original event
}
`$3
The
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 |`typescript
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.$3
The
component provides state management for controls.#### Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
|
value | string \| null | null | Reflects the last changed control's value |
| state | Record | {} | 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 |`typescript
interface StateChangeEventDetail {
name: string; // Control name
value: unknown; // New value
state: Record; // 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
- disabled state handling---
SSR Support
The package is SSR-safe.
initWebKit() is a no-op in server environments:`typescript
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 server
`For React/Next.js, all hooks check for browser environment:
`tsx
// This is safe to call on the server
const { ready, theme } = useWebKitContext();
// ready will be false on server, true after hydration
``---
- Website
- Demo
- 𝕏
- GitHub
- npm
Aaron Iker
Jakub Antalik
MIT © Aaron Iker