JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 81
  • Score
    100M100P100Q82559F
  • License MIT

Framework-agnostic, Light DOM web components. CSS is the source of truth.

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-ui

Set theme: <html data-ry-theme="light">light | dark | omit for OS preference Set body: <body data-ry-reset 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 data-ry-reset 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-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, avatar, name, role Quote card. Plain text children = quote

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
<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> value, label, size="sm|lg" Stat card
<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> title, price, featured Pricing tier. featured scales up with bold border
<ry-heading> size="sm|lg", align="center|right", divider, sub Section heading with optional subtitle

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 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>
<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>
<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 title="Free" price="$0/mo">
    <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 title="Pro" price="$19/mo">
    <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>
<ry-dropdown>
  <ry-button>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:

  1. ry-tokens.css — CSS custom properties (colors, spacing, etc.)
  2. ry-structure.css — Pure layout (no colors) + Preflight reset via data-ry-reset
  3. ry-theme.css — All visual styling (colors, shadows, borders)

Preflight Reset

Add data-ry-reset to <body> to normalize all elements (Tailwind Preflight-equivalent):

<body data-ry-reset>

Resets box-sizing, margins, padding, form element inheritance, media elements, lists, tables. Without it, only ry-* components are reset.

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
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