Form Controls
Layout
Navigation
Data Display
Feedback
Charts & Visualization
Media
Specialty
Commerce & Business
Accordion
Expandable content sections
What is Snice?
A TypeScript web component library with decorators, differential rendering, and separation of concerns.
How do I install it?
Run npm install snice to get started. Components are tree-shakeable.
Is it accessible?
Yes! All components follow WCAG guidelines with keyboard navigation and ARIA attributes.
html
<snice-accordion>
<snice-accordion-item item-id="faq-1" open>
<span slot="header">Question?</span>
<p>Answer content here.</p>
</snice-accordion-item>
</snice-accordion>
Elevated variant
Surface elevation
Items float with shadows instead of borders. Expanded items get higher elevation.
Standalone cards
Each item is a standalone card with gap spacing between them.
Smooth transitions
Shadow depth transitions smoothly on expand/collapse.
html
<snice-accordion variant="elevated">
<snice-accordion-item item-id="elev-1" open>
<span slot="header">Surface elevation</span>
<p>Items float with shadows.</p>
</snice-accordion-item>
</snice-accordion>
Action Bar
Positioned, animated container for contextual actions
html
<div style="position: relative"
@mouseenter=${() => bar.show()}
@mouseleave=${() => bar.hide()}>
Hover me
<snice-action-bar position="bottom" variant="pill">
<snice-button variant="text" circle size="small">
<svg slot="icon" viewBox="0 0 24 24"><!-- pencil --></svg>
</snice-button>
<snice-button variant="text" circle size="small">
<svg slot="icon" viewBox="0 0 24 24"><!-- clipboard --></svg>
</snice-button>
<snice-button variant="danger" circle size="small">
<svg slot="icon" viewBox="0 0 24 24"><!-- trash --></svg>
</snice-button>
</snice-action-bar>
</div>
html
<snice-action-bar
no-animation
position="top-right"
size="small"
>
<snice-button variant="text" circle size="small">
<svg slot="icon" viewBox="0 0 24 24"><!-- pencil --></svg>
</snice-button>
<snice-button variant="danger" circle size="small">
<svg slot="icon" viewBox="0 0 24 24"><!-- trash --></svg>
</snice-button>
</snice-action-bar>
Activity Feed
Chronological stream of user activities with filtering
javascript
<snice-activity-feed
group-by="date"
></snice-activity-feed>
feed.activities = [
{ id: '1',
actor: { name: 'Alice' },
action: 'created',
target: 'Project Alpha',
type: 'create',
timestamp: new Date().toISOString()
}
];
Alert
Contextual feedback messages
This is an informational message. Use it to provide helpful context.
Your changes have been saved successfully.
Please review your input before continuing.
An error occurred. Please try again.
html
<snice-alert variant="info" title="Information" dismissible>
This is an informational message.
</snice-alert>
<snice-alert variant="success" title="Success">
Your changes have been saved.
</snice-alert>
<snice-alert variant="error" title="Error">
An error occurred.
</snice-alert>
App Tiles
App launcher grid like Google's app drawer or a phone home screen
html
<snice-app-tiles columns="3">
<snice-app-tile name="Mail"
icon="mail"
color="rgb(220 38 38)"></snice-app-tile>
<snice-app-tile name="Videos"
icon="play_circle"
badge="5"></snice-app-tile>
</snice-app-tiles>
html
<!-- No icon = colored letter -->
<snice-app-tiles columns="3">
<snice-app-tile name="Slack"
color="rgb(97 31 105)">
</snice-app-tile>
<snice-app-tile name="AWS"
color="rgb(255 153 0)"
badge="!">
</snice-app-tile>
</snice-app-tiles>
html
<snice-app-tiles columns="1"
variant="list">
<snice-app-tile name="Dashboard"
icon="dashboard"
color="rgb(37 99 235)">
</snice-app-tile>
<snice-app-tile name="Messages"
icon="chat" badge="8">
</snice-app-tile>
</snice-app-tiles>
html
<snice-app-tiles columns="2"
variant="compact">
<snice-app-tile name="Dashboard"
icon="dashboard"
color="rgb(37 99 235)">
</snice-app-tile>
<snice-app-tile name="Messages"
icon="chat" badge="8">
</snice-app-tile>
</snice-app-tiles>
html
<!-- size: sm, md, lg, xl, 2xl -->
<snice-app-tiles columns="2"
size="xl">
<snice-app-tile name="Photos"
icon="photo_library">
</snice-app-tile>
</snice-app-tiles>
<!-- Custom size via CSS variable -->
<snice-app-tiles columns="2"
style="--snice-app-tile-icon-size:7rem">
<snice-app-tile name="Gallery"
icon="collections">
</snice-app-tile>
</snice-app-tiles>
js
tiles.columns = 3;
tiles.tiles = [
{ id: '1', name: 'Chrome',
icon: 'https://cdn.../chrome.svg' },
{ id: '2', name: 'Slack',
icon: 'https://cdn.../slack.svg' },
];
tiles.addEventListener('tile-click',
e => console.log(e.detail.tile));
Approval Flow
Visual approval chain with approver avatars, status indicators, and action buttons
javascript
<snice-approval-flow
id="flow"
orientation="horizontal"
current-step="step-3"
></snice-approval-flow>
<script>
flow.steps = [
{ id: 'step-1', approver: 'Alice Chen',
role: 'Manager', status: 'approved',
timestamp: 'Feb 25, 10:00 AM' },
{ id: 'step-2', approver: 'Bob Smith',
role: 'Director', status: 'approved',
comment: 'Looks good' },
{ id: 'step-3', approver: 'Carol Wu',
role: 'VP Finance', status: 'pending' }
];
flow.addEventListener('step-approve',
e => console.log(e.detail));
</script>
Audio Recorder
Voice recording (requests mic permission on record)
html
<snice-audio-recorder
show-controls
show-visualizer
show-timer
show-playback
></snice-audio-recorder>
Availability
Weekly availability grid with click/drag selection, presets, and configurable time slots
javascript
<snice-availability
id="avail"
granularity="60"
start-hour="8"
end-hour="20"
format="12h"
></snice-availability>
<script>
// Pre-fill with business hours
avail.value = [
{ day: 0, start: '09:00', end: '17:00' },
{ day: 1, start: '09:00', end: '17:00' },
{ day: 2, start: '09:00', end: '17:00' },
{ day: 3, start: '09:00', end: '17:00' },
{ day: 4, start: '09:00', end: '17:00' }
];
avail.addEventListener('availability-change',
e => console.log(e.detail.value));
</script>
Avatar Group
Stacked avatar list with overflow indicator
html
<snice-avatar-group id="group"
max="4" size="medium">
</snice-avatar-group>
<script>
group.avatars = [
{ name: 'Alice Chen',
src: 'assets/avatars/pravatar-80-1.jpg' },
{ name: 'Bob Rivera' },
{ name: 'Carol Kim',
src: 'assets/avatars/pravatar-80-3.jpg' },
{ name: 'Dave Patel' },
{ name: 'Eve Martinez' },
{ name: 'Frank Lee' }
];
</script>
<!-- Sizes: small, medium, large -->
<!-- max: visible count before +N -->
Avatar
User profile images with fallback initials
html
<snice-avatar name="Alice Johnson" size="large">
</snice-avatar>
<snice-avatar
src="avatar.jpg"
name="User"
></snice-avatar>
html
<!-- Shapes -->
<snice-avatar shape="circle" name="Circle"></snice-avatar>
<snice-avatar shape="square" name="Square"></snice-avatar>
<snice-avatar shape="rounded" name="Rounded"></snice-avatar>
<!-- Auto-generated colors from name -->
<snice-avatar name="Alice"></snice-avatar>
<snice-avatar name="Bob"></snice-avatar>
<!-- Custom fallback colors -->
<snice-avatar name="Red"
fallback-background="#dc2626"
fallback-color="#fff">
</snice-avatar>
Badge
Notification indicators and count displays
Messages
Online
Updates
html
<snice-badge count="5">
<snice-button>Messages</snice-button>
</snice-badge>
<!-- Live status — pulsing dot -->
<snice-badge dot pulse variant="success">
<snice-button>Online</snice-button>
</snice-badge>
<!-- Overflow: shows 99+ when count > max -->
<snice-badge count="150" max="99" variant="primary">
<snice-button>Updates</snice-button>
</snice-badge>
Status
Priority
Release
html
<snice-badge
inline
content="Active"
variant="success"
></snice-badge>
<!-- Default (neutral) for non-semantic labels -->
<snice-badge inline content="New"></snice-badge>
Banner
Full-width notification banners
html
<snice-banner
variant="info"
message="New version available!"
action-text="Update Now"
dismissible
></snice-banner>
<!-- Variants: info, success, warning, error -->
Binpack
Draggable bin-packing layout — items fill gaps like Tetris. Drag widgets to rearrange.
html
<snice-binpack draggable gap="8px"
column-width="80" row-height="80">
<div style="width:168px;height:168px">
Revenue widget...
</div>
<div style="width:80px;height:80px">
Users widget...
</div>
<!-- Items pack tightly, drag to rearrange -->
</snice-binpack>
<!-- Attributes -->
<snice-binpack
draggable <!-- enable drag-to-reorder -->
gap="8px" <!-- spacing between items -->
column-width="80" <!-- grid snap width -->
row-height="80" <!-- grid snap height -->
horizontal <!-- pack left-to-right -->
stagger="40" <!-- cascade transition delay -->
></snice-binpack>
Book
CSS-only page-flip book with 3D transforms, based on the Foundation book preview CodePen
Web Components
Jane Developer
Contents
Chapter I Custom Elements 3
Chapter II Shadow DOM 15
Chapter III Templates 27
Chapter I: Custom Elements
Web Components are a set of web platform APIs that allow you to create new custom, reusable, encapsulated HTML tags to use in web pages and web apps.
Custom Elements let you define your own HTML elements with custom behavior and styling using the CustomElementRegistry.
The Custom Elements specification lays the foundation for designing and using new types of DOM elements that are fully-featured and conforming.
Chapter I (continued)
A custom element's name must contain a hyphen. This requirement ensures forward compatibility with future HTML elements and avoids naming collisions.
Elements can observe attribute changes via attributeChangedCallback , enabling reactive property updates from HTML attributes alone.
The lifecycle callbacks — connected, disconnected, adopted, and attribute-changed — give full control over the element's behavior in the DOM.
Chapter II: Shadow DOM
Shadow DOM provides encapsulation for DOM and CSS. A shadow tree is a hidden subtree attached to an element in the regular DOM tree.
Styles defined inside a shadow root do not leak out, and external styles do not bleed in. This is true encapsulation at the platform level.
Slots allow composition — the host element's light DOM children are projected into designated positions within the shadow tree.
Chapter III: Templates
The <template> element holds HTML content that is not rendered when the page loads, but can be instantiated later using JavaScript.
Template content is parsed but inert — scripts don't run, images don't load, and styles don't apply until the content is cloned into the live DOM.
Combined with custom elements and shadow DOM, templates provide the final piece for building fully self-contained, reusable components.
html
<snice-book
title="Web Components"
author="Jane Developer"
cover-image="cover.jpg"
>
<snice-book-page>
<div slot="front">Title page</div>
<div slot="back">Contents</div>
</snice-book-page>
<snice-book-page>
<div slot="front">Chapter 1...</div>
</snice-book-page>
</snice-book>
<script>
book.nextPage();
book.goToPage(1);
book.addEventListener('page-turn',
e => console.log(e.detail));
</script>
Booking
Appointment booking widget with date picker, time slot selection, and confirmation form
javascript
<snice-booking
id="bk"
duration="30"
variant="stepper"
></snice-booking>
<script>
// Generate available dates
const dates = [];
for (let i = 1; i <= 14; i++) {
const d = new Date();
d.setDate(d.getDate() + i);
if (d.getDay() !== 0 && d.getDay() !== 6)
dates.push(d.toISOString().slice(0,10));
}
bk.availableDates = dates;
bk.availableSlots = dates.flatMap(
date => ['09:00','10:00','11:00',
'14:00','15:00','16:00'].map(
time => ({ date, time, duration: 30 })
));
bk.fields = [
{ name: 'name', label: 'Full Name',
type: 'text', required: true },
{ name: 'email', label: 'Email',
type: 'email', required: true },
{ name: 'notes', label: 'Notes',
type: 'textarea' }
];
bk.addEventListener('booking-confirm',
e => console.log(e.detail));
</script>
Breadcrumbs
Navigation path indicators with separators, icons, sizes, and collapsible paths
Default
With Icons
Collapsible (max-items=3)
html
<!-- Separators: / > » • | -->
<snice-breadcrumbs separator="/">
<snice-crumb href="/" label="Home"></snice-crumb>
<snice-crumb href="/docs" label="Docs"></snice-crumb>
<snice-crumb label="API" active></snice-crumb>
</snice-breadcrumbs>
<!-- With icons -->
<script>
el.items = [
{ label: 'Home', href: '/', icon: '🏠' },
{ label: 'Products', href: '/products', icon: '📦' },
{ label: 'Details' }
];
</script>
<!-- Collapse long paths -->
<snice-breadcrumbs max-items="3">...</snice-breadcrumbs>
<!-- Sizes: small, medium, large -->
<snice-breadcrumbs size="small">...</snice-breadcrumbs>
Button
All variants, sizes, shapes, and states
Default
Primary
Success
Warning
Danger
Text
html
<snice-button variant="primary">Primary</snice-button>
<snice-button variant="success">Success</snice-button>
<snice-button variant="danger" outline>Danger</snice-button>
<snice-button variant="primary" pill>Pill</snice-button>
<snice-button variant="primary" circle>+</snice-button>
Outline
Success
Danger
Warning
html
<snice-button variant="primary" outline>Outline</snice-button>
<snice-button variant="success" outline>Success</snice-button>
Small
Medium
Large
Pill
+
✓
×
html
<snice-button variant="primary" size="small">
Small
</snice-button>
<snice-button variant="primary" size="large">
Large
</snice-button>
<snice-button variant="primary" pill>Pill</snice-button>
<snice-button variant="primary" circle>+</snice-button>
Icon Start
Icon End
Search
Delete
html
<snice-button variant="primary">
<svg slot="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M14.6 1.6 3.75 12.75h6L8.75 22.4 20.25 9.75h-7.27L14.6 1.6Z"/>
</svg>
Icon Start
</snice-button>
<snice-button icon-placement="end">
<svg slot="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="m4.5 12.75 6 6 9-13.5"/>
</svg>
Icon End
</snice-button>
Loading
Disabled
html
<snice-button variant="primary" loading>Loading</snice-button>
<snice-button variant="primary" disabled>Disabled</snice-button>
Calendar
Date picker calendar with events and scheduled slots
javascript
<snice-calendar
id="cal"
highlight-today
></snice-calendar>
<script>
cal.events = [
{ id: 1, title: 'Sprint Review', start: inDays(1),
color: '#3b82f6', avatar: { src: 'assets/avatars/pravatar-40-alice.jpg?v=80938d7',
name: 'Dana Ives' } },
{ id: 2, title: 'Conference',
start: inDays(3), end: inDays(9),
color: '#22c55e', tooltip: 'Berlin, hall 2' },
{ id: 3, title: 'Planning', start: inDays(4),
color: '#f59e0b' }
];
</script>
Camera Annotate
Image annotation with camera capture, freehand drawing, and labeled annotations
Open Camera Annotate
html
<snice-camera-annotate></snice-camera-annotate>
Without Labels Panel
html
<!-- Hide the labels sidebar -->
<snice-camera-annotate
show-labels-panel="false"
></snice-camera-annotate>
<!-- Disable auto color rotation -->
<snice-camera-annotate
auto-rotate-colors="false"
></snice-camera-annotate>
Camera
Webcam capture (requires permission)
Open Camera
html
<snice-camera
show-controls
controls-position="bottom"
auto-start="false"
></snice-camera>
Camera Capture
Close
Candlestick
SVG-based OHLC candlestick chart for financial data
html + js
<snice-candlestick
id="chart"
show-volume
y-axis-format="currency"
></snice-candlestick>
<script>
chart.data = [
{ date: '2024-01-02', open: 185.09, high: 188.44, low: 183.89, close: 186.01, volume: 5820000 },
{ date: '2024-01-03', open: 186.50, high: 187.10, low: 183.42, close: 184.25, volume: 4910000 },
// ...more candles
];
</script>
html
<!-- Minimal: no volume, default format -->
<snice-candlestick id="chart">
</snice-candlestick>
<script>
chart.data = [
{ date: '2024-03-01', open: 170, high: 175,
low: 168, close: 173 },
{ date: '2024-03-04', open: 173, high: 178,
low: 171, close: 176 },
];
</script>
Card
Content containers with header, footer, and image slots
TECHNOLOGY
The Future of Web Components
Exploring emerging standards and the rise of framework-free development...
5 min read
Mar 15, 2025
DESIGN
UI/UX Best Practices
Essential design principles for intuitive, beautiful interfaces...
8 min read
Mar 14, 2025
DEVELOPMENT
Building Scalable Apps
Architect applications that grow with your business needs...
12 min read
Mar 13, 2025
SECURITY
Auth Patterns in 2025
Modern authentication strategies for web applications...
7 min read
Mar 12, 2025
Total Revenue
$48.2k
↑ 12.5% from last month
Active Users
2,847
↑ 8.1% from last week
Conversion Rate
3.24%
↓ 2% from last month
Avg. Response
142ms
↑ 15% faster
html
<!-- Blog card with gradient image slot -->
<snice-card variant="elevated" clickable>
<div slot="image" style="background:linear-gradient(...)">
<svg><!-- code icon --></svg>
</div>
<h4>Title</h4>
<p>Description...</p>
<div slot="footer">5 min read</div>
</snice-card>
<!-- Dashboard stat card -->
<snice-card variant="flat">
<p>Total Revenue</p>
<p style="font-size:1.5rem">$48.2k</p>
<p style="color:green">↑ 12.5%</p>
</snice-card>
Carousel
Image and content slider with controls, autoplay, and multi-slide views
html
<!-- With controls -->
<snice-carousel show-controls show-indicators loop>
<img src="photo1.jpg?v=80938d7">
<img src="photo2.jpg?v=80938d7">
</snice-carousel>
<!-- Autoplay -->
<snice-carousel
autoplay
autoplay-interval="2000"
loop
>...</snice-carousel>
<!-- Multiple slides per view -->
<snice-carousel
slides-per-view="3"
space-between="12"
>...</snice-carousel>
Cart
Shopping cart with line items, quantity controls, coupon field, and checkout summary
javascript
<snice-cart
id="cart"
tax-rate="8.5"
></snice-cart>
<script>
cart.items = [
{ id: '1', name: 'Wireless Headphones',
price: 99.99, quantity: 1,
image: 'headphones.jpg' },
{ id: '2', name: 'Yoga Mat Premium',
price: 49.99, quantity: 2,
variant: 'Sage Green' }
];
cart.addEventListener('checkout',
e => console.log(e.detail));
</script>
Chart & Sparkline
Line, bar, pie charts and inline mini charts
html
<snice-chart id="chart" type="line" height="200">
</snice-chart>
<snice-sparkline
type="line"
color="success"
width="80"
height="24"
></snice-sparkline>
<script>
chart.labels = ['Jan', 'Feb', 'Mar'];
chart.datasets = [{ label: 'Sales', data: [30, 45, 60] }];
</script>
Chat
Slack-style messaging — theming, markdown, bubble layouts, declarative messages
html
<!-- Default -->
<snice-chat current-user="You"></snice-chat>
<script>
chat.messages = [
{ id:'1', type:'text', author:'Alice',
content:'Hey! How’s it going?',
timestamp:new Date() }
];
</script>
html
<!-- Auto color per author -->
<snice-chat color-authors></snice-chat>
<!-- or explicit -->
chat.authorColors = {
Alice: '#e11d48', Bob: '#2563eb'
};
html
<!-- Render bodies as markdown -->
<snice-chat markdown></snice-chat>
chat.addMessage({
type:'text', author:'Alice',
format:'markdown',
content:'**Release 5.3** 🎉\n\n- colors\n- bubbles\n\n`npm i snice@latest`',
timestamp:new Date()
});
html
<!-- Mobile-OS aligned bubbles -->
<snice-chat
layout="bubbles"
current-user="You"
></snice-chat>
Hey, you around?
yep, one sec
check the **PR** then
html
<!-- Author messages as children -->
<snice-chat current-user="Me" color-authors>
<snice-chat-message slot="messages"
author="Alice">Hey, you around?</snice-chat-message>
<snice-chat-message slot="messages"
author="Me">yep, one sec</snice-chat-message>
<snice-chat-message slot="messages"
author="Alice" format="markdown">check the **PR**</snice-chat-message>
</snice-chat>
Checkbox
Toggle selections with checked, indeterminate, and loading states
html
<snice-checkbox label="Remember me" checked>
</snice-checkbox>
<snice-checkbox label="Indeterminate" indeterminate>
</snice-checkbox>
<!-- Sizes -->
<snice-checkbox label="Small" size="small"></snice-checkbox>
<snice-checkbox label="Large" size="large"></snice-checkbox>
<!-- States -->
<snice-checkbox label="Disabled" disabled></snice-checkbox>
<snice-checkbox label="Loading" loading></snice-checkbox>
<snice-checkbox label="Invalid" invalid></snice-checkbox>
Chip
Tags, filters, and selections
html
<!-- Status chips: pick a semantic variant -->
<snice-chip label="Active" variant="success"></snice-chip>
<snice-chip label="Pending" variant="warning"></snice-chip>
<snice-chip label="Failed" variant="error"></snice-chip>
<snice-chip label="Archived"></snice-chip>
html
<!-- Filter chips: click-to-toggle, neutral by default -->
<snice-chip label="All" selectable selected></snice-chip>
<snice-chip label="Open" selectable></snice-chip>
<snice-chip label="Closed" selectable></snice-chip>
<snice-chip label="Mine" selectable></snice-chip>
html
<!-- Tag-input style: X to remove -->
<snice-chip label="typescript" removable></snice-chip>
<snice-chip label="web-components" removable></snice-chip>
<snice-chip label="design-systems" removable></snice-chip>
html
<!-- Sizes -->
<snice-chip label="Small" size="small"></snice-chip>
<snice-chip label="Large" size="large"></snice-chip>
<!-- Shapes -->
<snice-chip label="Pill"></snice-chip> <!-- default -->
<snice-chip label="Rounded" shape="rounded"></snice-chip>
<snice-chip label="Square" shape="square"></snice-chip>
Code Block
Syntax-highlighted code display with formatting
import { element, property, render, dispatch } from 'snice';
@element('my-counter')
export class MyCounter extends HTMLElement {
@property({ type: Number }) count = 0;
@property({ type: Number }) step = 1;
@property({ type: Number }) min = 0;
@property({ type: Number }) max = 100;
increment() {
this.count = Math.min(this.count + this.step, this.max);
this.emitChange();
}
decrement() {
this.count = Math.max(this.count - this.step, this.min);
this.emitChange();
}
reset() {
this.count = 0;
this.emitChange();
}
@dispatch('count-change', { bubbles: true })
private emitChange() {
return { count: this.count, step: this.step };
}
@render()
template() {
const pct = ((this.count - this.min) / (this.max - this.min)) * 100;
return html`
<div class="counter">
<button @click="${this.decrement}">-</button>
<span class="value">${this.count}</span>
<button @click="${this.increment}">+</button>
</div>
<div class="bar" style="width:${pct}%"></div>
`;
}
}
html
<snice-code-block
language="snice"
grammar="grammars/snice.json?v=80938d7"
format="pretty"
show-line-numbers
copyable
filename="example.ts"
>
const x = 42;
</snice-code-block>
<!-- Custom formatter (same pattern as setHighlighter) -->
<script>
cb.setFormatter((code, language) => {
// format → then highlight runs automatically
return myPrettier.format(code, { parser: language });
});
</script>
Color Display
Color swatch with label
html
<snice-color-display
value="#3b82f6"
label="Primary"
show-swatch
show-label
></snice-color-display>
Color Picker
Interactive color selection
html
<snice-color-picker
value="#3b82f6"
label="Theme Color"
show-presets
></snice-color-picker>
html
<snice-color-picker value="#3b82f6" loading></snice-color-picker>
Command Palette
Keyboard-driven command search (Ctrl/Cmd+K)
Open Command Palette
or press Ctrl+K
html
<snice-command-palette
id="cmd"
placeholder="Type a command..."
></snice-command-palette>
<script>
cmd.commands = [
{ id: 'save', label: 'Save', shortcut: 'Ctrl+S' }
];
</script>
Comments
Threaded comment section with nested replies, likes, avatars, and timestamps
Countdown
Countdown timer to a target date with flip, circular, and simple variants
html
<snice-countdown
target="2026-12-31T00:00:00Z"
format="dhms"
variant="flip"
></snice-countdown>
<snice-countdown
variant="circular"
format="hms"
></snice-countdown>
<script>
cd.addEventListener('countdown-complete',
() => alert('Done!'));
</script>
Cropper
Image cropping with drag-to-resize, rotation, zoom, and aspect ratio lock
html
<snice-cropper
src="/photo.jpg?v=80938d7"
aspect-ratio="1.777"
output-type="jpeg"
></snice-cropper>
<script>
// Get cropped image
const blob = await cropper.crop();
// Rotate and zoom
cropper.rotate(90);
cropper.zoom(1.5);
cropper.reset();
</script>
Data Card
Key-value detail panels with grouped fields and inline editing
Customer Details
html
<snice-data-card id="card">
<span slot="title">Customer Details</span>
</snice-data-card>
<script>
card.fields = [
{ label: 'Name', value: 'Alice Johnson',
group: 'Contact' },
{ label: 'Email', value: 'alice@acme.co',
type: 'link', href: 'mailto:alice@acme.co',
group: 'Contact' },
{ label: 'Status', value: 'Active',
type: 'badge', badgeVariant: 'success',
group: 'Account' },
{ label: 'Balance', value: '$12,450',
type: 'currency', group: 'Account' }
];
</script>
Date Picker
Date selection with calendar, year picker, formats, and states
html
<snice-date-picker
label="Select Date"
clearable
></snice-date-picker>
<!-- Formats -->
<snice-date-picker format="mm/dd/yyyy"></snice-date-picker>
<snice-date-picker format="dd/mm/yyyy"></snice-date-picker>
<snice-date-picker format="yyyy-mm-dd"></snice-date-picker>
<snice-date-picker format="mmmm dd, yyyy"></snice-date-picker>
html
<!-- Sizes -->
<snice-date-picker size="small"></snice-date-picker>
<snice-date-picker size="large"></snice-date-picker>
<!-- Variants -->
<snice-date-picker variant="filled"></snice-date-picker>
<snice-date-picker variant="underlined"></snice-date-picker>
<!-- States -->
<snice-date-picker disabled></snice-date-picker>
<snice-date-picker readonly></snice-date-picker>
<snice-date-picker loading></snice-date-picker>
<snice-date-picker required helper-text="Required"></snice-date-picker>
<snice-date-picker invalid error-text="Invalid"></snice-date-picker>
html
<form>
<snice-date-picker
name="delivery-date"
value="2026-03-15"
format="dd/mm/yyyy"
min="2026-03-10"
max="2026-03-20"
required
></snice-date-picker>
</form>
<!-- FormData: delivery-date=2026-03-15 -->
Date Range Picker
Select a date range with calendar, presets, and year picker
html
<snice-date-range-picker
label="Date Range"
clearable
></snice-date-range-picker>
<snice-date-range-picker
label="Dual Column"
columns="2"
clearable
></snice-date-range-picker>
html
<snice-date-range-picker
id="preset-picker"
label="With Presets"
columns="2"
clearable
></snice-date-range-picker>
<script>
const picker = document.getElementById('preset-picker');
const today = new Date();
picker.presets = [
{ label: 'Last 7 days',
start: new Date(Date.now() - 7 * 86400000),
end: today },
{ label: 'Last 30 days',
start: new Date(Date.now() - 30 * 86400000),
end: today },
{ label: 'This month',
start: new Date(today.getFullYear(), today.getMonth(), 1),
end: new Date(today.getFullYear(), today.getMonth() + 1, 0) },
];
</script>
html
<!-- Sizes -->
<snice-date-range-picker size="small"></snice-date-range-picker>
<snice-date-range-picker size="large"></snice-date-range-picker>
<!-- States -->
<snice-date-range-picker disabled></snice-date-range-picker>
<snice-date-range-picker readonly></snice-date-range-picker>
<snice-date-range-picker loading></snice-date-range-picker>
<snice-date-range-picker required helper-text="Required"></snice-date-range-picker>
<snice-date-range-picker invalid error-text="Invalid"></snice-date-range-picker>
html
<form>
<snice-date-range-picker
name="booking"
start="2026-03-10"
end="2026-03-20"
min="2026-03-01"
max="2026-03-31"
clearable
required
></snice-date-range-picker>
<button type="submit">Submit</button>
<button type="reset">Reset</button>
</form>
Date Time Picker
Combined date and time selection with calendar, time columns, and year picker
html
<snice-date-time-picker
label="Event Start"
placeholder="Select date and time"
></snice-date-time-picker>
<!-- 12h + US date format -->
<snice-date-time-picker
time-format="12h"
date-format="mm/dd/yyyy"
></snice-date-time-picker>
<!-- Long date format -->
<snice-date-time-picker
date-format="mmmm dd, yyyy"
></snice-date-time-picker>
<!-- With seconds -->
<snice-date-time-picker show-seconds></snice-date-time-picker>
<!-- Clearable -->
<snice-date-time-picker
value="2026-03-15T10:30"
clearable
></snice-date-time-picker>
html
<!-- Sizes -->
<snice-date-time-picker size="small"></snice-date-time-picker>
<snice-date-time-picker size="large"></snice-date-time-picker>
<!-- States -->
<snice-date-time-picker disabled></snice-date-time-picker>
<snice-date-time-picker readonly></snice-date-time-picker>
<snice-date-time-picker loading></snice-date-time-picker>
<snice-date-time-picker required helper-text="Required"></snice-date-time-picker>
<snice-date-time-picker invalid error-text="Invalid"></snice-date-time-picker>
html
<form>
<snice-date-time-picker
name="appointment"
value="2026-03-10T14:05"
min="2026-03-01T00:00"
max="2026-03-31T23:59"
show-seconds
clearable
required
></snice-date-time-picker>
<button type="submit">Submit</button>
<button type="reset">Reset</button>
</form>
Diff
Code and text diff viewer with unified and split modes
html
<snice-diff
mode="unified"
line-numbers
context-lines="3"
></snice-diff>
<script>
diff.oldText = 'function hello() {\n'
+ ' return "hello";\n}';
diff.newText = 'function hello() {\n'
+ ' return "hello world";\n}';
</script>
Divider
Content separators in horizontal and vertical orientations
html
<snice-divider text="OR"></snice-divider>
<snice-divider orientation="vertical"></snice-divider>
<snice-divider variant="dashed"></snice-divider>
Doc
Rich text document editor with formatting toolbar, headings, lists, images, links, tables, and download as HTML/Markdown/Text
html
<snice-doc icons="material"></snice-doc>
<script>
editor.setHTML(`
<h1>Welcome!</h1>
<p>Rich text with <b>bold</b>,
<i>italic</i>, <u>underline</u></p>
<ul>
<li>Headings (H1, H2, H3)</li>
<li>Bullet and numbered lists</li>
<li>Images, links, and tables</li>
</ul>
`);
// Export as markdown
const md = editor.getMarkdown();
// Download as file
editor.downloadAs('markdown', 'my-doc.md');
</script>
Drawer
Slide-out panels from any edge with push, persist, and size options
Push Content
This drawer pushes sibling content.
Navigation
Dashboard
Projects
Settings
Details Panel
Additional information and actions can go here.
Notification Bar
You have 3 new notifications
Quick Actions
Save
Cancel
Left
Right
Top
Bottom
Push Content
html
<!-- Positions -->
<snice-drawer position="left">...</snice-drawer>
<snice-drawer position="right">...</snice-drawer>
<snice-drawer position="top">...</snice-drawer>
<snice-drawer position="bottom">...</snice-drawer>
<!-- Sizes -->
<snice-drawer size="small"></snice-drawer>
<snice-drawer size="large"></snice-drawer>
<snice-drawer size="full"></snice-drawer>
<!-- Push content with target -->
<snice-drawer id="my-drawer" push-content></snice-drawer>
<snice-drawer-target for="my-drawer">
<main>Content moves when drawer opens</main>
</snice-drawer-target>
<!-- Features -->
<snice-drawer no-backdrop></snice-drawer>
<snice-drawer persistent></snice-drawer>
Inline Mode
Sits in the document flow as a persistent sidebar. Use breakpoint to auto-switch between inline and overlay.
Sidebar
Home
Explore
Settings
Main Content
The drawer sits inline alongside the content, no overlay or backdrop.
html
<!-- Always inline -->
<snice-drawer inline position="left" size="small">
<span slot="title">Sidebar</span>
...
</snice-drawer>
<!-- Responsive: inline above 768px, overlay below -->
<snice-drawer breakpoint="768" position="left" size="small">
<span slot="title">Sidebar</span>
...
</snice-drawer>
Empty State
Placeholder for empty content areas
html
<snice-empty-state
title="No results found"
description="Try adjusting your search."
>
<svg slot="icon" viewBox="0 0 24 24"
fill="none" stroke="currentColor"
stroke-width="1.5">
<circle cx="11" cy="11" r="8"/>
<line x1="21" y1="21"
x2="16.65" y2="16.65"/>
</svg>
</snice-empty-state>
Estimate
Quote/estimate with optional line items, accept/decline actions, and expiry date
javascript
<snice-estimate
id="est"
estimate-number="EST-0042"
date="Feb 27, 2026"
expiry-date="Mar 15, 2026"
status="sent"
currency="$"
tax-rate="8"
></snice-estimate>
<script>
est.from = { name: 'Design Studio' };
est.to = { name: 'Client Corp' };
est.items = [
{ description: 'Brand Identity',
quantity: 1, unitPrice: 5000 },
{ description: 'Social Media Kit',
quantity: 1, unitPrice: 2000,
optional: true }
];
est.addEventListener('estimate-accept',
e => console.log(e.detail));
</script>
File Gallery
File management with previews and upload progress
html
<snice-file-gallery
accept="image/*"
multiple view="grid"
show-add-button allow-delete
></snice-file-gallery>
<script>
gallery.addEventListener(
'request/file-gallery-upload', (e) => {
const { file, onProgress } = e.detail;
e.preventDefault();
// Simulate upload progress
let p = 0;
const iv = setInterval(() => {
p = Math.min(1, p + 0.05);
onProgress(p);
if (p >= 1) clearInterval(iv);
}, 100);
});
</script>
File Upload
Drag-and-drop file selection
html
<snice-file-upload
label="Upload files"
accept="image/*,.pdf"
multiple
drag-drop
show-preview
max-files="5"
></snice-file-upload>
Flip Card
3D card flip with front and back faces, click or programmatic control
Click to flip
Back side
Vertical flip
Other side
html
<snice-flip-card
direction="horizontal"
style="width:240px;height:160px"
>
<div slot="front">Front content</div>
<div slot="back">Back content</div>
</snice-flip-card>
<script>
card.flip();
card.flipTo('back');
card.addEventListener('flip-change',
e => console.log(e.detail.side));
</script>
Flow
Node-based flow/diagram editor with draggable nodes, ports, and bezier curve edges
html + js
<snice-flow id="flow" snap-to-grid minimap>
</snice-flow>
<script>
const flow = document.getElementById('flow');
flow.nodes = [
{ id: 'start', x: 50, y: 100, label: 'Start',
outputs: [{ id: 'out', label: 'Out' }] },
{ id: 'process', x: 300, y: 80, label: 'Process',
inputs: [{ id: 'in', label: 'In' }],
outputs: [{ id: 'out', label: 'Out' }] },
{ id: 'end', x: 550, y: 100, label: 'End',
inputs: [{ id: 'in', label: 'In' }] },
];
flow.edges = [
{ id: 'e1', source: 'start', target: 'process',
sourcePort: 'out', targetPort: 'in' },
{ id: 'e2', source: 'process', target: 'end',
sourcePort: 'out', targetPort: 'in' },
];
</script>
Form Layout
Responsive form grid with configurable columns and label positions
html
<snice-form-layout columns="2" gap="medium">
<snice-input label="First Name"></snice-input>
<snice-input label="Last Name"></snice-input>
<snice-input label="Email"></snice-input>
<snice-input label="Phone"></snice-input>
</snice-form-layout>
<!-- Label positions: top (default), left, right -->
<snice-form-layout label-position="left">
...
</snice-form-layout>
<!-- Gaps: small, medium (default), large -->
<!-- Variants: default, compact, card -->
<snice-form-layout
columns="3"
gap="small"
variant="compact"
></snice-form-layout>
Funnel
SVG-based funnel chart for conversion tracking and pipeline visualization
html + js
<snice-funnel id="funnel" animation></snice-funnel>
<script>
funnel.data = [
{ label: 'Visitors', value: 12000 },
{ label: 'Signups', value: 5200 },
{ label: 'Trials', value: 2800 },
{ label: 'Paid', value: 1100 },
{ label: 'Enterprise', value: 340 }
];
</script>
html + js
<snice-funnel
variant="gradient"
orientation="horizontal"
animation
></snice-funnel>
<script>
funnel.data = [
{ label: 'Impressions', value: 50000, color: 'rgb(99 102 241)' },
{ label: 'Clicks', value: 18000, color: 'rgb(79 70 229)' },
{ label: 'Leads', value: 6400, color: 'rgb(67 56 202)' },
{ label: 'Sales', value: 1800, color: 'rgb(55 48 163)' }
];
</script>
Gantt
Project timeline with draggable task bars, zoom levels, and task groups
html
<snice-gantt zoom="week"></snice-gantt>
<script>
gantt.tasks = [
{ id: '1', name: 'Design',
start: '2026-03-01',
end: '2026-03-07',
progress: 80, group: 'Phase 1' },
{ id: '2', name: 'Development',
start: '2026-03-05',
end: '2026-03-15',
progress: 30, group: 'Phase 1' }
];
gantt.addEventListener('task-click',
e => console.log(e.detail.task));
</script>
Gauge
SVG-based gauge/meter for dashboards
html
<snice-gauge value="75" variant="primary" label="CPU"></snice-gauge>
<snice-gauge value="42" variant="success" label="Memory"></snice-gauge>
<snice-gauge value="90" variant="error" label="Disk"></snice-gauge>
<snice-gauge value="58" variant="warning" label="Network"></snice-gauge>
html
<snice-gauge value="30" size="small" variant="info" label="Small"></snice-gauge>
<snice-gauge value="60" size="medium" variant="primary" label="Medium"></snice-gauge>
<snice-gauge value="85" size="large" variant="success" label="Large"></snice-gauge>
Grid
Coordinate-based grid layout — place items at explicit positions with spanning. Drag widgets to rearrange with snap-to-grid.
html
<snice-grid draggable gap="8px"
column-width="80" row-height="80">
<!-- Place items at explicit grid coordinates -->
<div grid-col="0" grid-row="0"
grid-colspan="2" grid-rowspan="2">
Revenue (2x2 at column 0, row 0)
</div>
<div grid-col="2" grid-row="0">
Users (1x1 at column 2, row 0)
</div>
<div grid-col="2" grid-row="1"
grid-colspan="2">
Orders (2x1 at column 2, row 1)
</div>
</snice-grid>
<!-- Attributes -->
<snice-grid
draggable <!-- enable drag with snap -->
gap="8px" <!-- spacing between cells -->
column-width="80" <!-- cell width in px -->
row-height="80" <!-- cell height in px -->
columns="6" <!-- fixed column count -->
rows="4" <!-- fixed row count -->
stagger="40" <!-- cascade animation delay -->
></snice-grid>
Heatmap
GitHub-style contribution heatmap
html + js
<snice-heatmap id="heatmap"></snice-heatmap>
<script>
const heatmap = document.getElementById('heatmap');
const data = [];
const now = new Date();
for (let i = 0; i < 365; i++) {
const d = new Date(now);
d.setDate(d.getDate() - i);
if (Math.random() > 0.3) {
data.push({
date: d.toISOString().split('T')[0],
value: Math.floor(Math.random() * 10)
});
}
}
heatmap.data = data;
</script>
html
<snice-heatmap
color-scheme="blue"
weeks="26"
></snice-heatmap>
<snice-heatmap
color-scheme="purple"
weeks="26"
></snice-heatmap>
Icons
A built-in Heroicons set every component falls back to. Pass a name to any icon property — no icon font, no extra request
Search
Delete
Download
69 icons ship built in — open Showcase for the full catalogue.
html
<!-- A name from the built-in set -->
<snice-button icon="magnifying-glass">Search</snice-button>
<!-- Or your own image / emoji -->
<snice-button icon="/icons/custom.svg">Custom</snice-button>
<snice-button icon="🚀">Launch</snice-button>
<!-- Or slot an element to override entirely -->
<snice-button>
<span slot="icon" class="material-symbols-outlined">home</span>
Home
</snice-button>
Image
Lazy-loaded images with variants
html
<snice-image
src="assets/photos/sample-200.jpg?v=80938d7"
alt="Photo"
variant="circle"
size="medium"
lazy
></snice-image>
Input
Text fields with types, sizes, variants, and validation
html
<snice-input label="Email" type="email"
placeholder="Enter your email"></snice-input>
<snice-input label="Password" type="password"
password placeholder="Enter password"></snice-input>
<snice-input size="small" label="Small"></snice-input>
<snice-input variant="filled" label="Filled"></snice-input>
<snice-input invalid error-text="Invalid email"></snice-input>
html
<snice-input label="Search" placeholder="Search...">
<svg slot="prefix-icon" viewBox="0 0 24 24">
<!-- magnifying-glass -->
</svg>
</snice-input>
<snice-input label="Email">
<svg slot="prefix-icon" viewBox="0 0 24 24">
<!-- envelope -->
</svg>
<svg slot="suffix-icon" viewBox="0 0 24 24">
<!-- check-circle -->
</svg>
</snice-input>
html
<snice-input label="Loading" placeholder="Fetching data..." loading></snice-input>
Invoice
Professional invoice with line items, tax/discount calculations, and status tracking
javascript
<snice-invoice
id="inv"
invoice-number="INV-2026-001"
date="Feb 27, 2026"
due-date="Mar 27, 2026"
status="sent"
currency="USD"
tax-rate="10"
discount="5"
></snice-invoice>
<script>
inv.from = { name: 'Acme Corp',
address: '123 Business St' };
inv.to = { name: 'Client Inc',
address: '456 Market Ave' };
inv.items = [
{ description: 'Web Development',
quantity: 40, unitPrice: 150 },
{ description: 'UI Design',
quantity: 20, unitPrice: 120 }
];
</script>
Templates - click a sheet to zoom
classic
modern
ledger
ticket
ink
paper
Kanban
Drag-and-drop kanban board
html
<snice-kanban
id="kb"
allow-drag-drop
show-card-count
></snice-kanban>
<script>
kb.columns = [
{ id: 'todo', title: 'To Do', cards: [
{ id: '1', title: 'Task', labels: ['Design'] }
]}
];
</script>
Key Value
Key-value pair editor for HTTP headers, env vars, and config entries — with edit and view modes
html
<!-- Declarative -->
<snice-key-value label="Headers">
<snice-kv-pair
key="Content-Type"
value="application/json"
></snice-kv-pair>
<snice-kv-pair
key="Authorization"
value="Bearer token"
></snice-kv-pair>
</snice-key-value>
<!-- Imperative -->
<snice-key-value id="kv"
label="Env Vars"
></snice-key-value>
<script>
const kv = document.getElementById('kv');
kv.setItems([
{ key: 'NODE_ENV',
value: 'production' },
{ key: 'PORT', value: '3000' }
]);
kv.addEventListener('kv-change',
e => console.log(e.detail.items));
</script>
html
<!-- View mode (read-only display) -->
<snice-key-value
label="Response Headers"
mode="view"
></snice-key-value>
<!-- Fixed rows -->
<snice-key-value
rows="3"
label="Fixed Rows"
></snice-key-value>
html
<form>
<snice-key-value
name="metadata"
value='[{"key":"region",
"value":"us-east-1",
"description":"Primary region"}]'
show-description
required
></snice-key-value>
<button type="submit">Submit</button>
<button type="reset">Reset</button>
</form>
KPI
Key performance indicators with trends, sparklines, and sentiment colors
html
<snice-kpi
label="Revenue"
value="$127,450"
trend-value="+22.4%"
sentiment="up"
></snice-kpi>
<script>
kpi.trendData = [30, 45, 35, 50, 40, 60, 55, 70];
</script>
<!-- Sizes: small, medium, large -->
<snice-kpi label="Small" value="1,234"
size="small" sentiment="up"></snice-kpi>
<!-- color-value applies sentiment to value -->
<snice-kpi label="Sales" value="$12,345"
sentiment="up" color-value></snice-kpi>
Layouts
Twelve layout shells for common application shells: dashboards, landing pages, split views, and more.
Dashboard · header + toolbar + sidebar + main + rail
snice-layout-dashboard
Header
Toolbar
Sidebar
Right
Sidebar · persistent left nav, overlays on mobile
Split · two panes side-by-side
Centered · auth card, brand above, links below
Minimal · bare main, no chrome
Landing · hero + stacked sections
Blog · centred measure, optional rail
Card grid · responsive columns
Fullscreen · layered canvas, overlay + controls
Master detail · list beside the selected item
snice-layout-master-detail
Docs · nav tree, prose, on-this-page rail
Auth split · form beside a brand panel
html
<!-- Each layout consumes placards from the Snice router -->
<snice-layout-dashboard></snice-layout-dashboard>
<snice-layout-sidebar></snice-layout-sidebar>
<snice-layout-split></snice-layout-split>
<snice-layout-centered></snice-layout-centered>
<snice-layout-minimal></snice-layout-minimal>
<snice-layout-landing></snice-layout-landing>
<snice-layout-blog></snice-layout-blog>
<snice-layout-card></snice-layout-card>
<snice-layout-fullscreen></snice-layout-fullscreen>
<snice-layout-master-detail></snice-layout-master-detail>
<snice-layout-docs></snice-layout-docs>
<snice-layout-auth-split></snice-layout-auth-split>
Leaderboard
Ranked list with podium variant, avatars, change indicators, and dual API (declarative children or imperative setter)
html
<!-- Declarative (child elements) -->
<snice-leaderboard variant="podium"
title="Top Players">
<snice-leaderboard-entry
rank="1" name="Alice"
score="2,500" change="3"
highlighted>
</snice-leaderboard-entry>
<snice-leaderboard-entry
rank="2" name="Bob"
score="2,100" change="-1">
</snice-leaderboard-entry>
</snice-leaderboard>
<!-- Imperative (setEntries) -->
<script>
lb.setEntries([
{ rank: 1, name: 'Alice',
score: 2500, change: 3 },
{ rank: 2, name: 'Bob',
score: 2100, change: -1 },
]);
</script>
Link Preview
Social media-style link preview cards
html
<snice-link-preview
title="Snice - Beautiful Decorators"
description="Write clean, reactive web components..."
image="assets/photos/link-preview.jpg"
site-name="snice.dev"
url="https://snice.dev"
></snice-link-preview>
<snice-link-preview
variant="horizontal"
size="small"
title="Getting Started Guide"
description="Learn to build elements step by step"
site-name="snice.dev"
url="https://snice.dev/guide"
></snice-link-preview>
Link
Styled anchor elements with variants
Default Link
Primary
Secondary
Underlined
External Link
Disabled
html
<snice-link href="#" variant="primary">Primary</snice-link>
<snice-link href="#" underline>Underlined</snice-link>
<snice-link href="#" external>External</snice-link>
<snice-link href="#" disabled>Disabled</snice-link>
List
Structured lists with dividers, search, before/after slots, and rich content
With Icons & Dividers
2.4 MB
5.1 MB
876 KB
html
<snice-list dividers>
<snice-list-item heading="Document.pdf"
description="Updated 2h ago">
<svg slot="before"><!-- document icon --></svg>
<span slot="after">2.4 MB</span>
</snice-list-item>
</snice-list>
<!-- With search -->
<snice-list searchable dividers>
<snice-list-item heading="Alice"
description="alice@example.com">
<span slot="before">...</span>
</snice-list-item>
</snice-list>
Location
Address and coordinate display
html
<snice-location
name="Eiffel Tower"
address="5 Avenue Anatole France"
city="Paris"
country="France"
show-icon
clickable
></snice-location>
Login
Authentication forms with card, minimal, and default variants, configurable fields
html
<snice-login
variant="card"
title="Welcome Back"
action-text="Sign In"
show-remember-me
show-forgot-password
>
<p slot="subtitle">Sign in to your account</p>
<div slot="footer">
<a href="#">Sign up here</a>
</div>
</snice-login>
<!-- Variants: default, card, minimal -->
<!-- Sizes: small, medium, large -->
Map
Interactive tile-based map with markers, popups, and zoom controls
html
<snice-map zoom="13"></snice-map>
<script>
map.center = { lat: 51.505, lng: -0.09 };
map.markers = [
{ id: '1', lat: 51.505,
lng: -0.09, label: 'London',
popup: 'Hello London!' }
];
map.addEventListener('marker-click',
e => console.log(e.detail.marker));
map.fitBounds();
</script>
Markdown
GFM-compatible markdown renderer with sanitization and syntax highlighting
## Project Status
The build pipeline is **fully operational**. Here's what shipped:
### Features
- [x] GFM tables and task lists
- [x] Code block syntax highlighting
- [ ] Image lazy loading
### Code Example
```js
const greeting = (name) => {
return `Hello, ${name}!`;
};
```
> *Markdown is rendered safely with built-in sanitization.*
| Component | Status | Size |
|-----------|--------|------|
| Button | Stable | 2 KB |
| Modal | Stable | 4 KB |
| Table | Beta | 12 KB |
Declarative
Imperative
html
<snice-markdown theme="github">
## Hello World
**Bold** and *italic* text.
- Item 1
- Item 2
| Col A | Col B |
|-------|-------|
| One | Two |
</snice-markdown>
javascript
md.setContent('## Dynamic\n\n'
+ '- Item 1\n- Item 2\n\n'
+ '**Bold** and *italic*');
md.addEventListener('link-click',
e => console.log(e.detail.href));
Masonry
Pinterest-style grid layout with variable height items
Mountain Retreat
Minimalist cabin design in the Alps
Sunset Palette
Color inspiration for your next interior project
Dark Mode UI Kit
50+ components designed for dark interfaces
Breakfast Bowl
Healthy smoothie recipe with granola topping
Ceramic Collection
Handmade pottery from Japanese artisans
Coastal Living Room
Light and airy beach house inspiration
Workspace Setup
Developer desk organization ideas
html
<snice-masonry columns="3" gap="0.75rem">
<div class="pin-card">
<img src="photo.jpg?v=80938d7" class="pin-img">
<div class="pin-body">
<div class="pin-title">Mountain Retreat</div>
<div class="pin-desc">Minimalist cabin design</div>
</div>
</div>
<!-- ... more cards -->
</snice-masonry>
<!-- Auto-responsive columns -->
<snice-masonry columns="0" min-column-width="250px">
<div class="pin-card">...</div>
</snice-masonry>
Menu
Dropdown menus with keyboard navigation
Actions Menu
Edit
Duplicate
Delete
More Options
Download
Share
Archive
html
<snice-menu>
<snice-button slot="trigger" variant="primary">
Actions Menu
</snice-button>
<snice-menu-item>Edit</snice-menu-item>
<snice-menu-item>Duplicate</snice-menu-item>
<snice-menu-divider></snice-menu-divider>
<snice-menu-item variant="danger">Delete</snice-menu-item>
</snice-menu>
Profile
Settings
Billing
Sign Out
html
<!-- Avatar dropdown -->
<snice-menu>
<div slot="trigger" style="display:inline-flex;
align-items:center;gap:0.5rem">
<snice-avatar name="Sarah Chen" size="small"
shape="circle"></snice-avatar>
<span>Sarah Chen</span>
<svg width="12" height="12" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2.5"
stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
<snice-menu-item>Profile</snice-menu-item>
<snice-menu-item>Settings</snice-menu-item>
<snice-menu-divider></snice-menu-divider>
<snice-menu-item variant="danger">Sign Out</snice-menu-item>
</snice-menu>
Message Strip
Inline status messages with optional dismiss
This is an informational message.
Changes saved successfully.
Your session will expire in 5 minutes.
Unable to connect to the server.
html
<snice-message-strip variant="info">
This is an informational message.
</snice-message-strip>
<snice-message-strip variant="success" dismissible>
Changes saved successfully.
</snice-message-strip>
<snice-message-strip variant="warning" dismissible>
Your session will expire in 5 minutes.
</snice-message-strip>
<snice-message-strip variant="danger">
Unable to connect to the server.
</snice-message-strip>
Modal
Dialog overlays for focused interactions
Open Modal
Form Modal
html
<snice-modal id="my-modal" label="Confirm">
<h2 slot="header">Confirm Action</h2>
<p>Are you sure you want to proceed?</p>
<div slot="footer">
<snice-button variant="default"
onclick="this.closest('snice-modal').close()">
Cancel</snice-button>
<snice-button variant="primary"
onclick="this.closest('snice-modal').close()">
Confirm</snice-button>
</div>
</snice-modal>
Confirm Action
Are you sure you want to proceed? This action cannot be undone.
Cancel
Confirm
Edit Profile
Cancel
Save Changes
Music Player
Audio playback with playlist. Tracks: Piñaita — used with permission.
html
<snice-music-player
show-playlist
show-controls
show-volume
show-artwork
show-track-info
></snice-music-player>
<script>
player.tracks = [
{ id: '1', title: 'Track',
artist: 'Artist', src: 'url' }
];
</script>
Nav
Application navigation with flat, hierarchical, and grouped variants in horizontal or vertical orientations
Horizontal · flat (pill active)
app.snice.dev / dashboard
Dashboard
Welcome back, Team
Horizontal · active-style="text" (underline, no fill)
docs.snice.dev / dashboard
Dashboard
Everything you need, at a glance
Vertical · hierarchical
admin / products / list
Products/ Product List
Product List
Vertical · grouped (by category)
admin / system / settings
javascript
// Flat navigation
nav.update(placards, appContext, 'dashboard');
// Hierarchical (parent-child)
nav.update([
{ name: 'products', title: 'Products', icon: 'inventory_2' },
{ name: 'products/list', parent: 'products',
title: 'Product List' },
], ctx, 'products/list');
// Grouped (by category)
nav.update([
{ name: 'users', title: 'Users',
group: 'Management', icon: 'group' },
], ctx, 'settings');
// Variants: flat, hierarchical, grouped
// Orientation: horizontal, vertical
// active-style="text" = color-only highlight
Network Graph
Force-directed network/relationship graph visualization
html + js
<snice-network-graph
id="graph"
show-labels
></snice-network-graph>
<script>
graph.data = {
nodes: [
{ id: 'api', label: 'API Gateway', group: 'infra' },
{ id: 'web', label: 'Web App', group: 'frontend' },
{ id: 'db', label: 'Database', group: 'data' },
],
edges: [
{ source: 'web', target: 'api' },
{ source: 'api', target: 'db' },
],
};
</script>
html
<!-- Circular layout with weighted edges -->
<snice-network-graph
id="graph"
layout="circular"
show-labels
></snice-network-graph>
<script>
graph.data = {
nodes: [
{ id: 'alice', label: 'Alice', group: 'eng' },
{ id: 'bob', label: 'Bob', group: 'eng' },
{ id: 'carol', label: 'Carol', group: 'design' },
],
edges: [
{ source: 'alice', target: 'bob', weight: 3 },
{ source: 'bob', target: 'carol', weight: 1 },
],
};
</script>
Notification Center
Bell icon with unread badge and dropdown notification panel
html
<snice-notification-center
icon="notifications">
</snice-notification-center>
<script>
nc.notifications = [
{ id: '1', title: 'New comment',
message: 'Alice commented on your PR',
timestamp: '2 min ago',
icon: 'chat_bubble' },
{ id: '2', title: 'Deploy complete',
message: 'Build v2.4 succeeded',
timestamp: '10 min ago',
icon: 'check_circle', read: true }
];
</script>
Order Tracker
Order status timeline with tracking info, horizontal and vertical orientations
javascript
<snice-order-tracker
id="tracker"
tracking-number="1Z999AA10123456784"
carrier="UPS"
></snice-order-tracker>
<script>
tracker.steps = [
{ label: 'Ordered', status: 'completed',
timestamp: 'Feb 20, 2:30 PM' },
{ label: 'Shipped', status: 'completed',
timestamp: 'Feb 21, 9:15 AM' },
{ label: 'In Transit', status: 'current',
description: 'Package in transit' },
{ label: 'Delivered', status: 'pending' }
];
</script>
Org Chart
Organizational hierarchy with collapsible nodes and avatar placeholders
html
<snice-org-chart
direction="top-down"
></snice-org-chart>
<script>
chart.data = {
id: '1', name: 'CEO',
title: 'Chief Executive',
children: [
{ id: '2', name: 'CTO',
title: 'Technology',
children: [...] },
{ id: '3', name: 'CFO',
title: 'Finance' }
]
};
</script>
Pagination
Navigation for paged content
html
<snice-pagination current="1" total="10">
</snice-pagination>
<snice-pagination current="5" total="20"
siblings="1"></snice-pagination>
<snice-pagination current="3" total="5"
size="small" variant="rounded">
</snice-pagination>
Draw + Paint
Self-contained drawing canvas with built-in toolbar. Uses <snice-draw> under the hood — use it standalone for a bare canvas with your own UI.
html
<!-- All-in-one with built-in toolbar -->
<snice-paint></snice-paint>
<!-- With 2 custom color pickers -->
<snice-paint color-selects="2"></snice-paint>
<!-- Custom tools via slots -->
<snice-paint>
<button slot="tools">🔲 Rect</button>
<button slot="toolbar-end">💾 Save</button>
</snice-paint>
<!-- Bare canvas only (bring your own toolbar) -->
<snice-draw
width="800" height="600"
color="#3b82f6" stroke-width="3"
></snice-draw>
<script>
paint.addEventListener('color-select', (e) => {
console.log('Custom color:', e.detail.color);
});
</script>
PDF Viewer
Document viewer with page navigation, zoom, fit modes, and print/download
html
<snice-pdf-viewer
src="/documents/report.pdf"
fit="width"
zoom="1"
></snice-pdf-viewer>
<script>
viewer.addEventListener('pdf-loaded',
e => console.log(
'Pages:', e.detail.totalPages));
// Navigation
viewer.nextPage();
viewer.goToPage(5);
viewer.print();
viewer.download();
</script>
Permission Matrix
Role-permission grid with toggleable checkboxes, grouped permissions, and read-only mode
javascript
<snice-permission-matrix
id="pm"
></snice-permission-matrix>
<script>
pm.roles = [
{ id: 'admin', name: 'Admin' },
{ id: 'editor', name: 'Editor' },
{ id: 'viewer', name: 'Viewer' }
];
pm.permissions = [
{ id: 'read', name: 'Read',
group: 'Content' },
{ id: 'write', name: 'Write',
group: 'Content' },
{ id: 'delete', name: 'Delete',
group: 'Content' },
{ id: 'manage', name: 'Manage Users',
group: 'Admin' }
];
pm.matrix = {
admin: ['read','write','delete','manage'],
editor: ['read', 'write'],
viewer: ['read']
};
pm.addEventListener('permission-toggle',
e => console.log(e.detail));
</script>
Podcast Player
Podcast playback with RSS feed support, episode list, speed control, and sleep timer
html
<!-- Direct source -->
<snice-podcast-player
src="episode.mp3"
title="Episode Title"
show="Podcast Name"
artwork="cover.jpg"
skip-forward="30"
skip-back="15"
></snice-podcast-player>
<!-- RSS feed mode -->
<snice-podcast-player
from-rss="https://feed.example.com/rss"
></snice-podcast-player>
<script>
player.addEventListener('podcast-play',
e => console.log(e.detail));
player.setPlaybackRate(1.5);
</script>
Popover
Click-triggered interactive panel anchored to a trigger
Open popover
Popover panel
Interactive content lives here. Click outside or press Escape to dismiss.
Placement: bottom
Centered under the trigger.
No outside dismiss
Only Escape or the trigger closes this.
html
<snice-popover placement="bottom-start">
<snice-button slot="trigger">
Open popover
</snice-button>
<div>Interactive panel content</div>
</snice-popover>
<snice-popover placement="right" no-outside-dismiss>
...
</snice-popover>
Pricing Table
Pricing tier comparison with cards/table variants, billing toggle, and highlighted plans
declarative (cards)
table variant
Declarative
Imperative
html
<snice-pricing-table>
<snice-plan name="Free" price="0"
cta="Get Started">
<snice-feature name="1 user"
included></snice-feature>
<snice-feature
name="API"></snice-feature>
</snice-plan>
<snice-plan name="Pro" price="29"
annual-price="24" highlighted
badge="Popular" cta="Start Trial">
<snice-feature name="10 users"
included></snice-feature>
<snice-feature name="API"
included></snice-feature>
</snice-plan>
</snice-pricing-table>
javascript
pt.plans = [
{ name: 'Free', price: 0,
features: [
{ name: '1 user', included: true },
{ name: 'API', included: false }
],
cta: 'Get Started' },
{ name: 'Pro', price: 29,
annualPrice: 24,
features: [
{ name: '10 users', included: true },
{ name: 'API', included: true }
],
cta: 'Start Trial',
highlighted: true,
badge: 'Popular' }
];
pt.addEventListener('plan-select',
e => console.log(e.detail));
Product Card
E-commerce product display with image gallery, ratings, variant selectors, badges, favorites, and add-to-cart. Six variants: vertical, horizontal, compact, featured, minimal, and grid.
javascript
<snice-product-card
variant="featured"
name="MacBook Pro M4"
price="2499.00"
sale-price="2199.00"
badge="SALE"
badge-variant="sale"
rating="4.5"
review-count="1247"
stock-count="12"
in-stock
></snice-product-card>
<script>
card.images = ['hero1.jpg', 'hero2.jpg'];
card.variants = [
{ type: 'Color', options: ['#1d1d1f','#f5f5f7','#b8860b'] },
{ type: 'Storage', options: ['512GB','1TB','2TB'] }
];
card.addEventListener('favorite',
e => console.log(e.detail.favorited));
card.addEventListener('quick-view',
() => console.log('quick view'));
</script>
html
<!-- Vertical with badge + low stock -->
<snice-product-card
name="Wireless Headphones"
price="149.99" sale-price="99.99"
badge="NEW" badge-variant="new"
stock-count="3" in-stock
></snice-product-card>
<!-- Minimal: clean fashion look -->
<snice-product-card variant="minimal"
name="Cashmere Sweater"
price="245.00"
></snice-product-card>
<!-- Compact: small inline card -->
<snice-product-card variant="compact"
name="USB-C Cable 2m" price="19.99"
></snice-product-card>
html
<!-- Grid variant: optimized for product grids -->
<div style="display:grid;grid-template-columns:
repeat(auto-fill,minmax(180px,1fr));gap:1rem">
<snice-product-card variant="grid"
name="Running Shoes Pro"
price="129.99" sale-price="89.99"
badge="SALE" badge-variant="sale"
></snice-product-card>
<snice-product-card variant="grid"
name="Yoga Mat Premium"
price="49.99"
></snice-product-card>
</div>
html
<!-- Skeleton loading state -->
<snice-product-card loading>
</snice-product-card>
<snice-product-card loading variant="horizontal">
</snice-product-card>
Progress Ring
Circular progress indicators with customizable size and color
html
<snice-progress-ring
value="25"
show-value
size="small"
></snice-progress-ring>
<snice-progress-ring
value="60"
show-value
></snice-progress-ring>
<snice-progress-ring
value="85"
show-value
size="large"
color="#22c55e"
></snice-progress-ring>
<snice-progress-ring
value="100"
show-value
size="large"
label="Done"
color="#6366f1"
></snice-progress-ring>
Progress & Spinner
Linear progress bars, circular progress, and spinners
Download Progress
Upload Complete
Storage Warning
Processing
html
<snice-progress value="65" show-label></snice-progress>
<snice-progress value="100" color="success">
</snice-progress>
<snice-progress indeterminate></snice-progress>
<snice-spinner size="large"></snice-spinner>
<snice-progress
variant="circular"
value="75"
show-label
></snice-progress>
QR Code
Generate QR codes with colors, logos, and dot styles
html
<!-- Logo overlay -->
<snice-qr-code
value="https://snice.dev"
size="120" margin="6"
include-image image-url="logo.png" image-size="30"
fg-color="#2196f3" bg-color="#e3f2fd">
</snice-qr-code>
<!-- Text with independent color -->
<snice-qr-code
value="WIFI:T:WPA;S:MyNetwork;P:secret;;"
center-text="WiFi" text-fill-color="#fff"
fg-color="#ff9800">
</snice-qr-code>
QR Reader
QR code scanner (requires camera permission)
Open QR Scanner
html
<snice-qr-reader
auto-start="false"
></snice-qr-reader>
Scan QR Code
Close
Radio
Single-selection radio buttons with inline and block variants
html
<snice-radio name="plan" label="Free" value="free" checked>
</snice-radio>
<snice-radio name="plan" label="Pro" value="pro">
</snice-radio>
<!-- Sizes -->
<snice-radio label="Small" size="small"></snice-radio>
<snice-radio label="Large" size="large"></snice-radio>
<!-- States -->
<snice-radio label="Disabled" disabled></snice-radio>
<snice-radio label="Loading" loading></snice-radio>
Free forever
$12/mo
Contact us
html
<snice-radio
variant="block"
name="pricing"
value="free"
label="Free Plan"
description="For individuals and side projects"
checked
>
<span slot="suffix">Free forever</span>
</snice-radio>
<snice-radio
variant="block"
name="pricing"
value="pro"
label="Pro Plan"
description="For growing teams"
>
<span slot="suffix">$12/mo</span>
</snice-radio>
Range Slider
Dual-handle slider for selecting a value range
html
<snice-range-slider
min="0"
max="1000"
value-low="200"
value-high="800"
step="50"
show-tooltip
show-labels
></snice-range-slider>
<snice-range-slider
min="18" max="65"
value-low="25" value-high="45"
show-tooltip show-labels
></snice-range-slider>
<snice-range-slider disabled></snice-range-slider>
Rating
Star rating input with full and half precision, custom icons, and keyboard navigation
html
<snice-rating
value="3.5"
max="5"
precision="half"
size="large"
></snice-rating>
<snice-rating value="4" readonly>
</snice-rating>
<script>
rating.addEventListener('rating-change',
e => console.log(e.detail.value));
</script>
Receipt
Transaction receipt with merchant info, line items, totals, and payment method
javascript
<snice-receipt
id="rcpt"
receipt-number="RCT-48291"
date="Feb 27, 2026 3:42 PM"
tax="4.76"
payment-method="Visa ending in 4242"
></snice-receipt>
<script>
rcpt.merchant = { name: 'Coffee Shop',
address: '789 Main St' };
rcpt.items = [
{ name: 'Latte', quantity: 2,
price: 5.50 },
{ name: 'Muffin', quantity: 1,
price: 3.95 }
];
</script>
Templates - click a sheet to zoom
thermal
modern
ledger
ticket
ink
paper
Recipe
Interactive recipe card with ingredient scaling, step-by-step progress, and nutrition facts
javascript
recipe.title = 'Pasta Carbonara';
recipe.prepTime = 10;
recipe.cookTime = 20;
recipe.servings = 4;
recipe.difficulty = 'medium';
recipe.cuisine = 'Italian';
recipe.ingredients = [
{ name: 'Spaghetti', amount: 400, unit: 'g' },
{ name: 'Pancetta', amount: 200, unit: 'g' },
{ name: 'Eggs', amount: 4, unit: '' },
{ name: 'Parmesan', amount: 100, unit: 'g' }
];
recipe.steps = [
{ text: 'Boil pasta in salted water.',
time: 10 },
{ text: 'Fry pancetta until crispy.' },
{ text: 'Mix eggs and parmesan.' },
{ text: 'Combine off heat.' }
];
recipe.nutrition = {
calories: 650, protein: 28,
carbs: 72, fat: 24 };
Sankey
Flow diagram showing quantities between categories
html + js
<snice-sankey id="sankey" show-labels show-values animation>
</snice-sankey>
<script>
sankey.data = {
nodes: [
{ id: 'organic', label: 'Organic' },
{ id: 'paid', label: 'Paid Ads' },
{ id: 'social', label: 'Social' },
{ id: 'landing', label: 'Landing' },
{ id: 'blog', label: 'Blog' },
{ id: 'pricing', label: 'Pricing' },
{ id: 'signup', label: 'Sign Up' },
{ id: 'download', label: 'Download' },
{ id: 'purchase', label: 'Purchase' }
],
links: [
{ source: 'organic', target: 'landing', value: 3000 },
{ source: 'organic', target: 'blog', value: 2000 },
{ source: 'paid', target: 'landing', value: 4000 },
{ source: 'paid', target: 'pricing', value: 1500 },
{ source: 'social', target: 'landing', value: 1000 },
{ source: 'social', target: 'blog', value: 800 },
{ source: 'landing', target: 'signup', value: 5000 },
{ source: 'landing', target: 'download', value: 2000 },
{ source: 'blog', target: 'signup', value: 1500 },
{ source: 'blog', target: 'download', value: 1000 },
{ source: 'pricing', target: 'purchase', value: 1500 }
]
};
</script>
html + js
<snice-sankey show-labels show-values></snice-sankey>
<script>
sankey.data = {
nodes: [
{ id: 'solar', label: 'Solar', color: 'rgb(250 204 21)' },
{ id: 'wind', label: 'Wind', color: 'rgb(56 189 248)' },
{ id: 'gas', label: 'Gas', color: 'rgb(251 146 60)' },
{ id: 'grid', label: 'Grid', color: 'rgb(163 163 163)' },
{ id: 'home', label: 'Home', color: 'rgb(74 222 128)' },
{ id: 'ev', label: 'EV', color: 'rgb(129 140 248)' }
],
links: [
{ source: 'solar', target: 'grid', value: 120 },
{ source: 'wind', target: 'grid', value: 80 },
{ source: 'gas', target: 'grid', value: 200 },
{ source: 'grid', target: 'home', value: 300 },
{ source: 'grid', target: 'ev', value: 100 }
]
};
</script>
Segmented Control
Mutually exclusive option selector with sliding indicator
javascript
<snice-segmented-control
value="weekly"
></snice-segmented-control>
control.options = [
{ label: 'Daily', value: 'daily' },
{ label: 'Weekly', value: 'weekly' },
{ label: 'Monthly', value: 'monthly' }
];
Select
Searchable, multi-select, and custom dropdowns
United States
United Kingdom
Canada
Australia
Germany
France
Japan
Engineering
Design
Marketing
Sales
JavaScript
TypeScript
React
Vue
Node.js
Python
Rust
Go
Option A
Option B
Option C
Locked Value
Option X
Option Y
Option Z
html
<snice-select label="Country" searchable clearable>
<snice-option value="us" icon="flags/us.png">United States</snice-option>
</snice-select>
<snice-select label="Skills" multiple searchable>
<snice-option value="js">JavaScript</snice-option>
</snice-select>
<!-- Sizes: small, medium (default), large -->
<snice-select size="small">...</snice-select>
<snice-select disabled>...</snice-select>
Red
Green
Blue
Locked Value
html
<!-- Loading state -->
<snice-select label="Loading" loading></snice-select>
<!-- Lazy-load options on first open -->
<snice-select id="lazy" label="Lazy-loaded"></snice-select>
<script>
const el = document.getElementById('lazy');
el.addEventListener('select-open', async () => {
if (el.options.length) return;
el.loading = true;
const data = await fetch('/api/options').then(r => r.json());
el.options = data;
el.loading = false;
});
</script>
<!-- Free text entry (type anything, not just listed options) -->
<snice-select searchable allow-free-text clearable>
<snice-option value="red">Red</snice-option>
</snice-select>
html
<!-- Editable: text input trigger instead of button -->
<snice-select editable
label="Fruit"
placeholder="Type or select..."
></snice-select>
<script>
select.options = [
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
{ value: 'cherry', label: 'Cherry' }
];
</script>
<!-- Editable with custom typed values -->
<snice-select editable allow-free-text
label="Custom values"
placeholder="Type anything..."
></snice-select>
<!-- Sizes: small, medium (default), large -->
<snice-select editable size="small"></snice-select>
<snice-select editable disabled></snice-select>
Skeleton
Loading placeholders
html
<snice-skeleton
variant="circular"
width="48px"
height="48px"
></snice-skeleton>
<snice-skeleton variant="text" width="40%">
</snice-skeleton>
<snice-skeleton variant="rectangular" height="200px">
</snice-skeleton>
Slider
Range input with variants, ticks, and labels
html
<snice-slider
label="Volume"
value="75"
show-value
></snice-slider>
<snice-slider
label="Rating (1-5)"
min="1" max="5" step="1"
show-value show-ticks
variant="warning"
></snice-slider>
html
<snice-slider label="Loading" value="50" show-value loading></snice-slider>
Sortable
Drag-and-drop reorderable list with animated transitions
Design mockups
Write API endpoints
Set up CI pipeline
Code review
Deploy to staging
html
<snice-sortable>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</snice-sortable>
<!-- With drag handle -->
<snice-sortable handle=".handle">
<div>
<span class="handle">☰</span>
Item A
</div>
</snice-sortable>
<script>
sortable.addEventListener('sort-change',
e => console.log(
e.detail.oldIndex,
e.detail.newIndex));
</script>
Split Button
Primary action with a dropdown of secondary actions
javascript
<snice-split-button
label="Save"
variant="primary"
></snice-split-button>
button.actions = [
{ label: 'Save as Draft', value: 'draft' },
{ label: 'Save & Close', value: 'save-close' },
{ label: 'Discard', value: 'discard' }
];
Split Pane
Resizable split layouts
Left Panel
Drag the divider to resize
Right Panel
Content adjusts automatically
html
<snice-split-pane
direction="horizontal"
primary-size="40"
>
<div slot="primary">Left</div>
<div slot="secondary">Right</div>
</snice-split-pane>
Spotlight
Guided tour overlay that highlights elements with step-by-step instructions
Search bar
Notifications
Settings
Start Tour
html
<snice-spotlight></snice-spotlight>
<script>
spotlight.steps = [
{ target: '#search',
title: 'Search',
description: 'Find anything',
position: 'bottom' },
{ target: '#notifs',
title: 'Notifications',
description: 'Stay updated' },
{ target: '#settings',
title: 'Settings',
description: 'Customize your experience' }
];
spotlight.start();
</script>
Stat Group
Coordinated row of KPI stat cards with trends
html
<snice-stat-group id="stats"
variant="card">
</snice-stat-group>
<script>
stats.stats = [
{ label: 'Revenue', value: '$48.2K',
trend: 'up', trendValue: '+12.5%' },
{ label: 'Users', value: '2,340',
trend: 'up', trendValue: '+8.1%' },
{ label: 'Bounce', value: '24.3%',
trend: 'down', trendValue: '-3.2%' },
{ label: 'Sessions', value: '18.7K',
trend: 'neutral', trendValue: '0%' }
];
</script>
<!-- Variants: card, minimal, bordered -->
Step Input
Numeric stepper with increment/decrement buttons
html
<snice-step-input
value="1"
min="0"
max="99"
></snice-step-input>
<!-- Decimal step -->
<snice-step-input
value="20" min="-10" max="50" step="0.5"
></snice-step-input>
<!-- Wrap: max rolls to min and vice versa -->
<snice-step-input
value="1" min="1" max="12" wrap
></snice-step-input>
<!-- Sizes: small, medium (default), large -->
<snice-step-input size="small"></snice-step-input>
<snice-step-input disabled></snice-step-input>
Stepper
Multi-step process indicator with horizontal/vertical orientations, panels, error states, and clickable navigation
html
<snice-stepper
id="stepper"
current-step="1"
clickable
></snice-stepper>
<script>
stepper.steps = [
{ label: 'Account', description: 'Create account' },
{ label: 'Profile', description: 'Setup profile' },
{ label: 'Settings', description: 'Configure' },
{ label: 'Complete', description: 'All done!' }
];
</script>
html
<snice-stepper
orientation="vertical"
current-step="1"
clickable
></snice-stepper>
<!-- Error state -->
<script>
stepper.steps = [
{ label: 'Upload', status: 'completed' },
{ label: 'Validate', status: 'error' },
{ label: 'Process', status: 'pending' }
];
</script>
With Panels
Step 1: Account Info
Enter your email and choose a password.
Step 2: Profile
Tell us about yourself.
Step 3: Preferences
Customize your experience.
Email notifications
Weekly digest
All Done!
Your account is ready.
Get Started
Previous
Next
html
<snice-stepper id="wizard" clickable>
<snice-stepper-panel>
<h4>Step 1: Account Info</h4>
<snice-input placeholder="Email"></snice-input>
<snice-input placeholder="Password"
type="password"></snice-input>
</snice-stepper-panel>
<snice-stepper-panel>
<h4>Step 2: Profile</h4>
<snice-input placeholder="Full Name">
</snice-input>
</snice-stepper-panel>
<!-- Panels auto-show/hide with step -->
</snice-stepper>
javascript
stepper.steps = [
{ label: 'Upload', status: 'completed' },
{ label: 'Validate', status: 'error' },
{ label: 'Process', status: 'pending' },
{ label: 'Complete', status: 'pending' }
];
stepper.currentStep = 1;
Switch
Toggle switches with on/off labels, sizes, and states
Notifications
Dark Mode
Disabled
html
<snice-switch checked>Notifications</snice-switch>
<snice-switch>Dark Mode</snice-switch>
<snice-switch disabled>Disabled</snice-switch>
<!-- Sizes -->
<snice-switch size="small" label="Small"></snice-switch>
<snice-switch size="large" label="Large"></snice-switch>
<!-- On/Off labels inside the track -->
<snice-switch label-on="ON" label-off="OFF">
Power
</snice-switch>
<!-- States -->
<snice-switch loading>Loading</snice-switch>
<snice-switch invalid>Invalid</snice-switch>
Table
Data tables with 24 built-in cell formats, grouping, aggregation, sorting, filtering, selection, editing, virtualization, and remote data
javascript
table.columns = [
{ key: 'product', label: 'Product', sortable: true },
{ key: 'revenue', label: 'Revenue', type: 'currency',
currencyFormat: { currency: 'USD', decimals: 0 } },
{ key: 'growth', label: 'Growth', type: 'percent' },
{ key: 'rating', label: 'Rating', type: 'rating' },
{ key: 'progress', label: 'Completion', type: 'progress' },
{ key: 'trend', label: 'Trend', type: 'sparkline' },
{ key: 'status', label: 'Status', type: 'tag' }
];
// 24 built-in formats: text, number, currency, accounting,
// scientific, fraction, percentage, date, boolean, rating,
// progress, sparkline, duration, filesize, tags, status,
// email, phone, link, color, image, JSON, location, actions
Tabs
Tabbed content with transitions and four placement directions
placement: top
Overview
Features
Getting Started
Component Library
Snice ships 64 production-ready web components built entirely with TypeScript. Each component uses Shadow DOM for style encapsulation and works standalone or as part of a larger application.
Framework agnostic — works with React, Vue, Angular, or vanilla JS.
What's Included
✓ Differential rendering
✓ TypeScript decorators
✓ Client-side routing
✓ Dark & light themes
✓ Form validation
✓ Accessibility built-in
Quick Start
Install from npm and import only what you need. Each component can also be loaded as a standalone script with zero configuration.
npm install snice
placement: bottom
Design
Develop
Deploy
Design System
Every component follows a consistent design language with customizable CSS properties. Swap color palettes, adjust spacing, or override individual parts.
Supports ::part() selectors for deep customization.
Developer Experience
TypeScript decorators handle boilerplate: @element registers the component, @property creates reactive attributes, @render defines the template. Less code, fewer bugs.
Production Ready
Tree-shakeable ESM builds, standalone IIFE scripts, and zero runtime dependencies. Each component is under 50 KB minified. Ship only what you use.
placement: start
Basics
Props
Events
Import the component and add it to your HTML. Attributes map directly to properties.
Use placement, transition, and active to control behavior.
Listen for tab-change to respond when the active tab switches.
placement: end
v4.5
v4.4
v4.3
Added component import validation, build improvements, and new standalone output formats.
Duplicate element registration now warns instead of erroring. Improved dev ergonomics.
Website improvements, release automation, and CI pipeline for automatic publishing.
html
<snice-tabs transition="fade">
<snice-tab slot="nav">Overview</snice-tab>
<snice-tab slot="nav">Features</snice-tab>
<snice-tab-panel>
<h4>Component Library</h4>
<p>64 production-ready components...</p>
</snice-tab-panel>
<snice-tab-panel>
<p>Differential rendering, routing...</p>
</snice-tab-panel>
</snice-tabs>
<!-- Bottom placement -->
<snice-tabs placement="bottom"
transition="slide">
...
</snice-tabs>
<!-- Vertical -->
<snice-tabs placement="start">...</snice-tabs>
<snice-tabs placement="end">...</snice-tabs>
Tag Input
Tag/pill input with autocomplete suggestions and keyboard navigation
html
<snice-tag-input
label="Technologies"
placeholder="Add a tag..."
max-tags="5"
></snice-tag-input>
<script>
input.suggestions = [
'JavaScript', 'TypeScript',
'Python', 'Rust', 'Go'
];
input.value = ['JavaScript'];
input.addEventListener('tag-add',
e => console.log(e.detail.tag));
input.addEventListener('tag-remove',
e => console.log(e.detail.tag));
</script>
Tag
Display-only tags and tokens with variants, sizes, and removable option
Default
Primary
Success
Warning
Danger
Info
Outline
Pill
Removable
Small
Large
html
<snice-tag variant="primary">Primary</snice-tag>
<snice-tag variant="success">Success</snice-tag>
<snice-tag variant="danger">Danger</snice-tag>
<!-- Outline style -->
<snice-tag variant="primary" outline>Outline</snice-tag>
<!-- Pill shape -->
<snice-tag variant="success" pill>Pill</snice-tag>
<!-- Removable (emits tag-remove) -->
<snice-tag variant="danger" removable>
Removable
</snice-tag>
<!-- Sizes: small, medium, large -->
<snice-tag size="small">Small</snice-tag>
<snice-tag size="large">Large</snice-tag>
Terminal
Interactive command-line interface
html
<snice-terminal id="term" prompt="$ "></snice-terminal>
<script>
term.writeln('Welcome!', 'info');
term.addEventListener('terminal-command', (e) => {
term.writeln('You typed: ' + e.detail.command);
});
</script>
Testimonial
Quote card with author info, avatar, star rating, and multiple variants
html
<snice-testimonial
quote="This framework transformed
our workflow."
author="Sarah Chen"
role="CTO"
company="Nexus Labs"
avatar="avatar.jpg"
rating="5"
variant="card"
></snice-testimonial>
<!-- Variants: card, minimal, featured -->
<snice-testimonial
quote="Outstanding experience."
author="Marcus Rivera"
variant="featured"
></snice-testimonial>
Textarea
Multi-line text input with auto-grow and character count
html
<snice-textarea label="Message" rows="3"
placeholder="Type your message..."></snice-textarea>
<snice-textarea label="Limited" maxlength="100">
</snice-textarea>
<snice-textarea auto-grow rows="2"
label="Auto-grow"></snice-textarea>
html
<snice-textarea label="Loading" placeholder="Fetching content..." rows="3" loading></snice-textarea>
Time Picker
Time selection with scrollable hour/minute columns
html
<snice-time-picker
label="Meeting Time"
value="09:30"
step="15"
></snice-time-picker>
<!-- 12-hour format with AM/PM -->
<snice-time-picker
label="12-Hour Format"
value="14:00"
format="12h"
></snice-time-picker>
<!-- Constrained time range -->
<snice-time-picker
min-time="08:00"
max-time="18:00"
step="30"
></snice-time-picker>
<!-- Clearable -->
<snice-time-picker clearable value="12:00"></snice-time-picker>
html
<!-- Sizes -->
<snice-time-picker size="small"></snice-time-picker>
<snice-time-picker size="large"></snice-time-picker>
<!-- States -->
<snice-time-picker disabled></snice-time-picker>
<snice-time-picker readonly></snice-time-picker>
<snice-time-picker loading></snice-time-picker>
<snice-time-picker required helper-text="Required"></snice-time-picker>
<snice-time-picker invalid error-text="Invalid"></snice-time-picker>
html
<form>
<snice-time-picker
name="appointment"
value="14:05:10"
format="12h"
step="5"
min-time="09:00:00"
max-time="17:00:00"
show-seconds
clearable
required
></snice-time-picker>
<button type="submit">Submit</button>
<button type="reset">Reset</button>
</form>
Time Range Picker
Vertically stacked time slot picker with click-and-drag range selection
html
<snice-time-range-picker
start-time="08:00"
end-time="18:00"
granularity="30"
value='[{"start":"09:00","end":"11:00"}]'
></snice-time-range-picker>
html
<!-- 12h format, hourly, multiple selection -->
<snice-time-range-picker
start-time="06:00"
end-time="22:00"
granularity="60"
format="12h"
multiple
disabled-ranges='[{"start":"12:00","end":"13:00"}]'
></snice-time-range-picker>
Timeline
Chronological event display with multiple positions and orientations
javascript
<!-- Vertical positions -->
<snice-timeline position="left"></snice-timeline>
<snice-timeline position="alternate"></snice-timeline>
<snice-timeline position="right"></snice-timeline>
<!-- Horizontal -->
<snice-timeline orientation="horizontal">
</snice-timeline>
timeline.items = [
{ title: 'Started', timestamp: 'Jan',
variant: 'info' },
{ title: 'Testing', timestamp: 'Mar',
variant: 'warning' },
{ title: 'Launched', timestamp: 'Apr',
variant: 'success' }
];
Timer
Countdown and stopwatch timers
html
<snice-timer mode="stopwatch"></snice-timer>
<snice-timer mode="timer" initial-time="60">
</snice-timer>
Toast
Temporary notification messages
Success
Error
Warning
Info
html
<snice-toast-container id="toasts" position="bottom-right">
</snice-toast-container>
<script>
toasts.show('Saved!', { type: 'success' });
</script>
Tooltip
Contextual information on hover or click
Top
Right
Bottom
Left
Click Trigger
html
<snice-tooltip content="Tooltip on top" position="top">
<snice-button>Top</snice-button>
</snice-tooltip>
<snice-tooltip content="Click me!" trigger="click">
<snice-button variant="primary">
Click Trigger
</snice-button>
</snice-tooltip>
Intrinsic Tooltips
Attribute-based — no wrapper element needed
Top
Right
Bottom
Left
Click Trigger
html
<button tooltip="Tooltip on top">Top</button>
<button tooltip="Tooltip on right"
style="--tooltip-position: right">
Right
</button>
<button tooltip="Click me!"
style="--tooltip-trigger: click">
Click Trigger
</button>
<!-- Tooltip observer activates automatically when tooltip component loads -->
Tree
Hierarchical data with expand/collapse, checkboxes, keyboard navigation, and lazy loading
javascript
tree.nodes = [
{ id: 'src', label: 'src', icon: '\ud83d\udcc1', expanded: true,
children: [
{ id: 'index.ts', label: 'index.ts', icon: '\ud83d\udcc4' },
{ id: 'app.ts', label: 'app.ts', icon: '\ud83d\udcc4' }
]
}
];
// Checkboxes with cascading selection
<snice-tree show-checkboxes
selection-mode="multiple"
></snice-tree>
// API: expandAll(), collapseAll(), checkNode(id),
// getCheckedNodes(), selectNode(id)
Treemap
Hierarchical data as nested rectangles with drill-down navigation
html + js
<snice-treemap id="treemap" show-labels show-values>
</snice-treemap>
<script>
treemap.data = {
label: 'Portfolio',
value: 0,
children: [
{ label: 'Tech', value: 0, children: [
{ label: 'AAPL', value: 4200 },
{ label: 'MSFT', value: 3800 },
{ label: 'GOOG', value: 2900 },
{ label: 'NVDA', value: 2100 }
]},
{ label: 'Finance', value: 0, children: [
{ label: 'JPM', value: 1800 },
{ label: 'BAC', value: 1200 }
]},
{ label: 'Health', value: 0, children: [
{ label: 'JNJ', value: 1600 },
{ label: 'PFE', value: 900 }
]}
]
};
</script>
html + js
<snice-treemap
show-labels
color-scheme="cool"
></snice-treemap>
<script>
treemap.data = {
label: 'Disk Usage',
value: 0,
children: [
{ label: 'Documents', value: 42 },
{ label: 'Photos', value: 28 },
{ label: 'Videos', value: 18 },
{ label: 'Music', value: 8 },
{ label: 'Other', value: 4 }
]
};
</script>
User Card
Profile cards with avatar, contact info, and social links
html
<snice-user-card
name="Sarah Chen"
avatar="avatar.png"
role="Lead Engineer"
company="Acme Inc"
email="sarah@acme.io"
location="San Francisco, CA"
status="online"
></snice-user-card>
<snice-user-card
name="James Wilson"
role="Designer"
status="away"
variant="compact"
></snice-user-card>
Video Player
Full-featured player with custom controls, keyboard shortcuts, PiP, and fullscreen
html
<snice-video-player
src="video.mp4"
poster="poster.jpg"
controls
></snice-video-player>
<script>
player.play();
player.seekTo(30);
player.setPlaybackRate(1.5);
player.requestFullscreen();
// Keyboard: Space=play/pause,
// F=fullscreen, M=mute,
// Arrow keys=seek/volume
</script>
Virtual Scroller
Efficiently renders thousands of items by virtualizing the DOM — only visible items are rendered
Top
Middle
Bottom
5,000 items — only ~15 rendered
javascript
scroller.items = Array.from({ length: 5000 }, (_, i) => ({
id: i,
data: { name: `User ${i + 1}`,
email: `user${i + 1}@example.com` }
}));
scroller.renderItem = (item) => `
<div style="display:flex;align-items:center;
gap:10px;padding:12px 16px;
border-bottom:1px solid rgba(128,128,128,0.15)">
<div class="avatar">${item.data.name[0]}</div>
<div>
<div>${item.data.name}</div>
<div style="opacity:0.5">${item.data.email}</div>
</div>
</div>`;
scroller.scrollToIndex(2500); // jump to any index
Waterfall
Bridge chart showing cumulative effect of sequential positive and negative values
javascript
chart.data = [
{ label: 'Revenue',
value: 1000, type: 'total' },
{ label: 'Product Sales',
value: 420 },
{ label: 'Services',
value: 250 },
{ label: 'Refunds',
value: -80 },
{ label: 'Operating Costs',
value: -350 },
{ label: 'Tax',
value: -120 },
{ label: 'Net Profit',
value: 1120, type: 'total' }
];
chart.addEventListener('bar-click',
e => console.log(e.detail.item));
Weather
Weather display card with current conditions, details, and multi-day forecast
javascript
weather.unit = 'celsius';
weather.variant = 'full';
weather.data = {
temp: 22,
condition: 'Partly Cloudy',
humidity: 65,
wind: 12,
forecast: [
{ day: 'Mon', high: 24, low: 18,
condition: 'Sunny' },
{ day: 'Tue', high: 20, low: 15,
condition: 'Rain' },
{ day: 'Wed', high: 22, low: 16,
condition: 'Cloudy' }
]
};
Work Order
Service work order with task checklist, parts list, time tracking, and cost summary
javascript
<snice-work-order
id="wo"
wo-number="WO-2026-0184"
priority="high"
status="in-progress"
labor-rate="85"
></snice-work-order>
<script>
wo.customer = { name: 'Office Park LLC',
phone: '(555) 123-4567' };
wo.tasks = [
{ description: 'Inspect units',
assignee: 'Mike', hours: 2,
completed: true },
{ description: 'Replace filters',
assignee: 'Mike', hours: 3 }
];
wo.parts = [
{ name: 'HVAC Filter 20x25',
partNumber: 'HF-2025',
quantity: 8, unitCost: 24.99 }
];
</script>
Templates - click a sheet to zoom
field-service
detailed
ledger
ticket
ink
paper