Package Exports
- @ryanhelsing/ry-ui
- @ryanhelsing/ry-ui/css/ry-base.css
- @ryanhelsing/ry-ui/css/ry-components.css
- @ryanhelsing/ry-ui/css/ry-layout.css
- @ryanhelsing/ry-ui/css/ry-structure.css
- @ryanhelsing/ry-ui/css/ry-theme.css
- @ryanhelsing/ry-ui/css/ry-tokens.css
- @ryanhelsing/ry-ui/css/ry-ui.css
- @ryanhelsing/ry-ui/themes/antigravity.css
- @ryanhelsing/ry-ui/themes/dark.css
- @ryanhelsing/ry-ui/themes/light.css
- @ryanhelsing/ry-ui/themes/ocean.css
Readme
ry-ui
Framework-agnostic, Light DOM web components. Zero dependencies. CSS is the source of truth.
Setup (2 lines)
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ryanhelsing/ry-ui/dist/css/ry-ui.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@ryanhelsing/ry-ui/dist/ry-ui.js"></script># or npm
npm install @ryanhelsing/ry-uiSet theme: <html data-ry-theme="light"> — light | dark | omit for OS preference
Set body: <body style="background: var(--ry-color-bg); color: var(--ry-color-text);">
DON'T / DO
DON'T write flexbox/grid CSS for page layout.
DO: <ry-page><ry-header>H</ry-header><ry-main>M</ry-main><ry-footer>F</ry-footer></ry-page>
DON'T write a custom modal with backdrop, focus trap, escape handling.
DO: <ry-button modal="m">Open</ry-button><ry-modal id="m" title="T">Content</ry-modal>
DON'T write a slide-out drawer with CSS transforms.
DO: <ry-button drawer="d">Open</ry-button><ry-drawer id="d" side="left">Content</ry-drawer>
DON'T write tab switching logic or CSS.
DO: <ry-tabs><ry-tab title="A" active>A</ry-tab><ry-tab title="B">B</ry-tab></ry-tabs>
DON'T write a custom select dropdown with keyboard navigation.
DO: <ry-select placeholder="Pick"><ry-option value="a">A</ry-option></ry-select>
DON'T write CSS variables for colors, spacing, shadows.
DO: Use --ry-color-*, --ry-space-*, --ry-radius-*, --ry-shadow-* tokens.
DON'T write button styles with hover/active/focus states.
DO: <ry-button variant="primary">Click</ry-button>
DON'T write toast/notification CSS and JS.
DO: RyToast.success('Saved!') / RyToast.error('Failed')
DON'T write accordion expand/collapse logic.
DO: <ry-accordion><ry-accordion-item title="Q" open>A</ry-accordion-item></ry-accordion>
DON'T write a toggle switch from scratch.
DO: <ry-switch name="notify" checked></ry-switch>
Full Page Template
<!DOCTYPE html>
<html lang="en" data-ry-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My App</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ryanhelsing/ry-ui/dist/css/ry-ui.css">
</head>
<body style="background: var(--ry-color-bg); color: var(--ry-color-text);">
<ry-page>
<ry-header sticky>
<ry-cluster>
<strong>My App</strong>
<ry-nav>
<a href="/" aria-current="page">Home</a>
<a href="/about">About</a>
</ry-nav>
</ry-cluster>
<ry-actions>
<ry-theme-toggle themes="light,dark"></ry-theme-toggle>
</ry-actions>
</ry-header>
<ry-main>
<ry-section>
<h1>Hello World</h1>
<p>Your content here.</p>
</ry-section>
</ry-main>
<ry-footer>Built with ry-ui</ry-footer>
</ry-page>
<script type="module" src="https://cdn.jsdelivr.net/npm/@ryanhelsing/ry-ui/dist/ry-ui.js"></script>
</body>
</html>Clean Syntax
Wrap markup in <ry> to use unprefixed tags:
<ry>
<accordion>
<accordion-item title="FAQ" open>No ry- prefix needed.</accordion-item>
</accordion>
</ry>Component Catalog
Layout (CSS-only, no JS)
| Component | Attributes | Description |
|---|---|---|
<ry-page> |
— | Root page container, flex column, min-height 100dvh |
<ry-header> |
sticky |
Flex row, space-between. sticky pins to top |
<ry-main> |
— | Content area, max-width 1200px, centered |
<ry-footer> |
— | Footer with border-top |
<ry-section> |
— | Block section with bottom margin |
<ry-grid> |
cols="1-6|auto-fit|auto-fill", cols-sm, cols-md, cols-lg |
CSS grid. Details |
<ry-stack> |
gap="sm|md|lg|xl" |
Vertical flex column |
<ry-cluster> |
gap="sm|md|lg" |
Horizontal flex row, wraps |
<ry-split> |
resizable, persist="key" |
Two-column with drag resize. Details |
<ry-center> |
— | Flex center (both axes) |
<ry-nav> |
— | Horizontal nav links. Active: a[aria-current="page"] |
<ry-logo> |
— | Inline-flex, bold text |
<ry-actions> |
— | Flex row for action buttons |
<ry-divider> |
vertical |
Horizontal line; vertical for inline separator |
<ry-aside> |
— | Sidebar content area |
Interactive Components
| Component | Key Attributes | Events |
|---|---|---|
<ry-button> |
variant="primary|secondary|outline|ghost|danger|accent", size="sm|lg", disabled, pressed, modal="id", drawer="id" |
ry:click |
<ry-button-group> |
name, value |
ry:change {value} |
<ry-toggle-button> |
pressed, name, value, size, icon, disabled |
ry:change {pressed, value} |
<ry-modal> |
id, title |
Trigger: <ry-button modal="id">. Details |
<ry-drawer> |
id, position="left|right", size |
Trigger: <ry-button drawer="id">. Details |
<ry-accordion> |
— | Container for accordion-items. Details |
<ry-accordion-item> |
title, open |
Collapsible section |
<ry-tabs> |
— | Children: <ry-tab title="..." active>. Details |
<ry-dropdown> |
— | Details |
<ry-select> |
placeholder, name, value, disabled |
ry:change {value}. Children: <ry-option> |
<ry-combobox> |
placeholder, name, value, disabled |
ry:change {value, label}, ry:input — searchable dropdown |
<ry-switch> |
checked, disabled, name |
ry:change {value, label} — value is "true"/"false" string |
<ry-tooltip> |
content, position |
Details |
<ry-toast> |
— | RyToast.success(), .error(), .warning(), .info(). Details |
<ry-slider> |
min, max, step, value, color, disabled |
ry:change {value}. Details |
<ry-knob> |
min, max, step, value, color, size |
ry:change {value}. Details |
<ry-number-select> |
min, max, step, value, arrows, prefix, suffix |
ry:change {value}. Details |
<ry-color-picker> |
value, format |
ry:change {value}. Details |
<ry-color-input> |
value, format |
ry:change {value} |
<ry-gradient-picker> |
value |
ry:change {value} |
<ry-tree> |
data (JSON) |
ry:select, ry:move. Details |
<ry-tag> |
removable |
ry:remove |
<ry-tag-input> |
name, value, placeholder |
ry:change {tags} |
<ry-carousel> |
autoplay, interval |
ry:change {index} |
<ry-theme-toggle> |
themes="light,dark" |
Cycles through themes |
<ry-theme-panel> |
theme, mode |
Floating theme/mode selector. Persists to localStorage |
<ry-testimonial> |
stars |
Quote card with avatar, name, role slots |
Display Components
| Component | Key Attributes | Description |
|---|---|---|
<ry-card> |
interactive, href |
Card container. interactive adds click/keyboard. href navigates |
<ry-badge> |
variant="primary|success|warning|danger|accent" |
Pill badge. Custom: style="--ry-badge-color: #8B5CF6" |
<ry-alert> |
type="info|success|warning|danger" |
Alert box with optional [slot="title"] |
<ry-field> |
label, error, hint |
Form field wrapper. Details |
<ry-icon> |
name |
SVG icon from registry |
<ry-code> |
language, title |
Syntax-highlighted code block |
<ry-hero> |
size="sm|lg", full-bleed, align="left" |
Marketing hero section |
<ry-stat> |
size="sm|lg" |
Stat card with slot="value", slot="label" |
<ry-feature> |
icon |
Feature card with icon |
<ry-feature-grid> |
cols="2|3|4" |
Responsive grid for feature cards |
<ry-pricing> |
— | Container for pricing cards |
<ry-pricing-card> |
featured |
Pricing tier. featured scales up with bold border |
Patterns
Grid
<!-- Fixed columns (auto-responsive: 3-6 → 2 at ≤1024px → 1 at ≤640px) -->
<ry-grid cols="3">...</ry-grid>
<!-- Explicit per-breakpoint -->
<ry-grid cols="5" cols-md="3" cols-sm="1">...</ry-grid>
<!-- Fluid auto-fit -->
<ry-grid cols="auto-fit">...</ry-grid>
<ry-grid cols="auto-fit" style="--ry-grid-min: 240px">...</ry-grid>Split Layout
<ry-split resizable persist="sidebar" style="--ry-split-width: 400px">
<div>Main content</div>
<div>Resizable sidebar — drag, arrow keys, double-click to reset</div>
</ry-split>CSS vars: --ry-split-width, --ry-split-min-width, --ry-split-max-width
Keyboard: Arrow (±10px), Shift+Arrow (±50px), Home/End, double-click reset
Event: ry:resize { width }
Forms
<ry-field label="Email" hint="We'll never share your email">
<input type="email" placeholder="you@example.com">
</ry-field>
<ry-field label="Password" error="Must be at least 8 characters">
<input type="password">
</ry-field>Error hides hint automatically. Set error="" to clear.
Button Group
<ry-button-group name="billing" value="monthly">
<ry-button value="monthly">Monthly</ry-button>
<ry-button value="annually">Annually</ry-button>
</ry-button-group>Button Variants
<ry-button>Default</ry-button>
<ry-button variant="primary">Primary</ry-button>
<ry-button variant="secondary">Secondary</ry-button>
<ry-button variant="outline">Outline</ry-button>
<ry-button variant="ghost">Ghost</ry-button>
<ry-button variant="danger">Danger</ry-button>
<ry-button variant="accent">Accent</ry-button>
<ry-button size="sm">Small</ry-button>
<ry-button size="lg">Large</ry-button>Modal & Drawer
<ry-button modal="confirm">Open Modal</ry-button>
<ry-modal id="confirm" title="Confirm Action">
<p>Are you sure?</p>
<ry-cluster>
<ry-button variant="danger">Delete</ry-button>
<ry-button variant="ghost">Cancel</ry-button>
</ry-cluster>
</ry-modal>
<ry-button drawer="settings">Settings</ry-button>
<ry-drawer id="settings" position="right" size="400px">
<h3>Settings</h3>
</ry-drawer>Nav Bar
<ry-header sticky>
<ry-cluster>
<ry-logo>MyApp</ry-logo>
<ry-divider vertical></ry-divider>
<ry-nav>
<a href="/" aria-current="page">Home</a>
<a href="/docs">Docs</a>
</ry-nav>
</ry-cluster>
<ry-actions>
<ry-button variant="ghost" size="sm">Login</ry-button>
<ry-button size="sm">Sign Up</ry-button>
</ry-actions>
</ry-header>Hero
<ry-hero>
<h1>Build faster with ry-ui</h1>
<p>Framework-agnostic components for any app.</p>
<ry-cluster>
<ry-button size="lg">Get Started</ry-button>
<ry-button variant="outline" size="lg">View Docs</ry-button>
</ry-cluster>
</ry-hero>Pricing
<ry-pricing>
<ry-pricing-card>
<h3>Free</h3>
<div class="ry-pricing__price">$0<span>/mo</span></div>
<ul class="ry-check-list">
<li>3 projects</li>
<li>Basic support</li>
</ul>
<ry-button variant="outline">Get Started</ry-button>
</ry-pricing-card>
<ry-pricing-card featured>
<h3>Pro</h3>
<div class="ry-pricing__price">$19<span>/mo</span></div>
<ul class="ry-check-list">
<li>Unlimited projects</li>
<li>Priority support</li>
</ul>
<ry-button>Upgrade</ry-button>
</ry-pricing-card>
</ry-pricing>Interactive Card Grid
<ry-grid cols="3">
<ry-card interactive href="/feature-a">
<h3>Feature A</h3>
<p>Description</p>
</ry-card>
</ry-grid>Dropdown Menu
<ry-dropdown>
<ry-button slot="trigger">Actions</ry-button>
<ry-menu>
<ry-menu-item>Edit</ry-menu-item>
<ry-menu-item>Duplicate</ry-menu-item>
<ry-menu-item>Delete</ry-menu-item>
</ry-menu>
</ry-dropdown>Events
All events prefixed with ry::
element.addEventListener('ry:change', (e) => console.log(e.detail));
element.addEventListener('ry:open', () => {});
element.addEventListener('ry:close', () => {});
element.addEventListener('ry:click', () => {});Programmatic Control
document.querySelector('ry-modal').open();
document.querySelector('ry-modal').close();
document.querySelector('ry-drawer').toggle();
document.querySelector('ry-select').value = 'new-value';CSS Token System
All visual properties use CSS custom properties. Override in your own CSS to customize.
Colors
| Token | Purpose |
|---|---|
--ry-color-primary / -hover / -active |
Primary action color |
--ry-color-secondary / -hover / -active |
Secondary muted color |
--ry-color-accent / -hover / -active |
Accent/highlight color |
--ry-color-success |
Green for positive states |
--ry-color-warning |
Yellow/orange for caution |
--ry-color-danger / -hover |
Red for destructive actions |
--ry-color-info |
Blue for informational |
--ry-color-text / -muted / -inverse |
Text colors |
--ry-color-bg / -subtle / -muted |
Background colors |
--ry-color-border / -strong |
Border colors |
--ry-color-overlay |
Modal/drawer backdrop |
Each color also has -bg and -text variants for alert/badge backgrounds.
Spacing
--ry-space-{0,1,2,3,4,5,6,8,10,12,16,20} — 0 to 5rem
Typography
| Token | Value |
|---|---|
--ry-font-sans |
system-ui stack |
--ry-font-mono |
ui-monospace stack |
--ry-text-{xs,sm,base,lg,xl,2xl,3xl,4xl} |
0.75rem to 2.25rem |
--ry-font-{normal,medium,semibold,bold} |
400 to 700 |
Borders & Shadows
| Token | Value |
|---|---|
--ry-radius-{none,sm,md,lg,xl,2xl,full} |
0 to 9999px |
--ry-shadow-{sm,md,lg,xl} |
Elevation shadows |
--ry-border-width |
1px |
Transitions
| Token | Value |
|---|---|
--ry-duration-{fast,normal,slow} |
100ms, 200ms, 300ms |
--ry-ease / -in / -out |
Cubic bezier easing |
Z-Index
| Token | Value |
|---|---|
--ry-z-dropdown |
1000 |
--ry-z-sticky |
1020 |
--ry-z-modal-backdrop |
1040 |
--ry-z-modal |
1050 |
--ry-z-tooltip |
1070 |
--ry-z-toast |
1080 |
Theming
Three CSS layers, loaded in order:
- ry-tokens.css — CSS custom properties (colors, spacing, etc.)
- ry-structure.css — Pure layout (no colors)
- ry-theme.css — All visual styling (colors, shadows, borders)
Custom Theme
Load structure-only and bring your own:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ryanhelsing/ry-ui/dist/css/ry-structure.css">
<link rel="stylesheet" href="your-tokens.css">
<link rel="stylesheet" href="your-theme.css">Override Tokens
No build step needed:
:root {
--ry-color-primary: oklch(0.541 0.218 293);
--ry-color-primary-hover: oklch(0.491 0.234 292);
--ry-radius-md: 0;
}Themes & Modes
Theme and mode are independent:
<html data-ry-theme="ocean" data-ry-mode="dark">Themes: default, ocean, none (structure only)
Modes: auto (OS preference), light, dark
Use <ry-theme-panel> for an interactive floating selector.
TypeScript
import { RyElement, RyButton, RyToast } from '@ryanhelsing/ry-ui';
RyToast.success('Saved!');
document.querySelector('ry-select')?.addEventListener('ry:change', (e: CustomEvent) => {
console.log(e.detail.value);
});
// Extend components
class MyWidget extends RyElement {
setup() {
this.on(this, 'click', () => this.emit('activate'));
}
}Icon Registry
Built-in: settings, heart, star, chevron-up, chevron-down, chevron-left, chevron-right, check, x, plus, minus, search, sun, moon, copy, trash, edit, eye, folder, file, drag
import { registerIcon, registerIcons } from '@ryanhelsing/ry-ui';
registerIcon('custom', '<svg>...</svg>');
registerIcons({ 'app-logo': '<svg>...</svg>' });Vendoring
Copy into your project instead of using CDN:
npm pack @ryanhelsing/ry-ui && tar -xf ryanhelsing-ry-ui-*.tgz
cp -r package/dist ./vendor/ry-ui && rm -rf package ryanhelsing-ry-ui-*.tgz<link rel="stylesheet" href="/vendor/ry-ui/css/ry-ui.css">
<script type="module" src="/vendor/ry-ui/ry-ui.js"></script>Detailed Docs
Per-component docs with full attributes, events, and examples:
| Doc | Components |
|---|---|
| layout | page, header, main, footer, section, grid, stack, cluster, split, center, card, nav, divider |
| button | button, toggle-button |
| button-group | button-group |
| accordion | accordion, accordion-item |
| tabs | tabs, tab |
| modal | modal |
| drawer | drawer |
| dropdown | dropdown, menu, menu-item |
| tooltip | tooltip |
| toast | toast |
| forms | field, select, switch |
| slider | slider |
| knob | knob |
| number-select | number-select |
| color | color-picker, color-input, gradient-picker |
| tree | tree |
| display | badge, alert, icon, code |
| theme-toggle | theme-toggle |
| theming | tokens, custom themes, structure-only loading |
AI-Friendly
This package includes a .claude/skills/ry-ui-builder skill so Claude Code can build with these components automatically. The detailed docs above serve as the complete agent reference.
License
MIT