UNPKG

@ryanhelsing/ry-ui

Version:

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

1,765 lines • 62.2 kB
<ry-page>

  <ry-header sticky>
    <ry-cluster>
      <strong>ry-ui</strong>
      <span>Kitchen Sink</span>
    </ry-cluster>

  </ry-header>

  <ry-theme-panel></ry-theme-panel>

  <ry-main>

    <!-- Sticky -->
    <ry-section>
      <h2>Sticky</h2>
      <ry-example title="sticky" stacked>
        <template>
<div style="height: 150px; overflow-y: auto; border: 1px solid var(--ry-color-border); border-radius: var(--ry-radius-lg);">
  <header sticky style="background: var(--ry-color-bg); padding: var(--ry-space-3); border-bottom: 1px solid var(--ry-color-border);">
    <strong>Sticky header</strong>
  </header>
  <stack style="padding: var(--ry-space-4);">
    <p>Scroll down...</p>
    <p>Keep scrolling...</p>
    <p>The header sticks!</p>
    <p>More content here...</p>
    <p>Almost there...</p>
    <p>Bottom reached.</p>
  </stack>
</div>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>sticky</code></td><td>boolean</td><td>Sticks to top of scroll container</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Grid -->
    <ry-section>
      <h2>Grid</h2>
      <ry-example title="grid">
        <template>
<grid cols="3">
  <card>One</card>
  <card>Two</card>
  <card>Three</card>
</grid>
        </template>
      </ry-example>
      <ry-example title="grid auto-fit">
        <template>
<grid cols="auto-fit" style="--ry-grid-min: 200px">
  <card>Auto</card>
  <card>Fit</card>
  <card>Cards</card>
  <card>Wrap</card>
  <card>Fluidly</card>
</grid>
        </template>
      </ry-example>
      <ry-example title="grid responsive breakpoints">
        <template>
<grid cols="5" cols-md="3" cols-sm="1">
  <card>1</card>
  <card>2</card>
  <card>3</card>
  <card>4</card>
  <card>5</card>
</grid>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>cols</code></td><td>1-6 | auto-fit | auto-fill</td><td>Number of columns or fluid mode</td></tr>
          <tr><td><code>cols-sm</code></td><td>1-3</td><td>Columns at ≤640px</td></tr>
          <tr><td><code>cols-md</code></td><td>1-4</td><td>Columns at 641-1024px</td></tr>
          <tr><td><code>cols-lg</code></td><td>2-6</td><td>Columns at ≥1025px</td></tr>
          <tr><td><code>--ry-grid-min</code></td><td>CSS length</td><td>Min column width for auto-fit/auto-fill (default 280px)</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Stack -->
    <ry-section>
      <h2>Stack</h2>
      <ry-example title="stack">
        <template>
<stack gap="sm">
  <card>Stacked item 1</card>
  <card>Stacked item 2</card>
  <card>Stacked item 3</card>
</stack>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>gap</code></td><td>sm | md | lg</td><td>Vertical spacing</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Cluster -->
    <ry-section>
      <h2>Cluster</h2>
      <ry-example title="cluster">
        <template>
<cluster>
  <badge>Tag 1</badge>
  <badge>Tag 2</badge>
  <badge>Tag 3</badge>
  <badge>Tag 4</badge>
</cluster>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>gap</code></td><td>sm | md | lg</td><td>Horizontal spacing</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Card -->
    <ry-section>
      <h2>Card</h2>
      <ry-example title="card">
        <template>
<card>
  <h3>Card Title</h3>
  <p>Card content goes here.</p>
  <actions>
    <button>Action</button>
    <button variant="ghost">Cancel</button>
  </actions>
</card>
        </template>
      </ry-example>
      <ry-example title="interactive cards" stacked>
        <template>
<grid cols="3">
  <card interactive>
    <h3>Hover Me</h3>
    <p>All cards lift on hover. Interactive cards get a stronger effect with primary border.</p>
  </card>
  <card interactive href="#card-demo">
    <h3>With Link</h3>
    <p>Interactive cards with <code>href</code> navigate on click or Enter key.</p>
  </card>
  <card interactive>
    <h3>Keyboard Accessible</h3>
    <p>Tab to focus, Enter or Space to activate. Emits <code>ry:click</code>.</p>
  </card>
</grid>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>interactive</code></td><td>boolean</td><td>Clickable card with hover lift, focus ring, keyboard support</td></tr>
          <tr><td><code>href</code></td><td>URL</td><td>Navigate on click (requires <code>interactive</code>)</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Button -->
    <ry-section>
      <h2>Button</h2>
      <ry-example title="button">
        <template>
<cluster>
  <button>Primary</button>
  <button variant="secondary">Secondary</button>
  <button variant="outline">Outline</button>
  <button variant="ghost">Ghost</button>
  <button variant="danger">Danger</button>
  <button variant="accent">Accent</button>
  <button pressed>Pressed</button>
</cluster>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>variant</code></td><td>primary | secondary | outline | ghost | danger | accent</td><td>Button style</td></tr>
          <tr><td><code>size</code></td><td>sm | md | lg</td><td>Button size</td></tr>
          <tr><td><code>pressed</code></td><td>boolean</td><td>Pressed/active toggle state</td></tr>
          <tr><td><code>modal</code></td><td>modal-id</td><td>Opens modal on click</td></tr>
          <tr><td><code>drawer</code></td><td>drawer-id</td><td>Opens drawer on click</td></tr>
        </tbody>
      </table>
    </ry-section>


    <!-- Split -->
    <ry-section>
      <h2>Split</h2>
      <ry-example title="split" stacked>
        <template>
<split style="--ry-split-width: 200px; height: 120px;">
  <card>Main content (flex: 1)</card>
  <card>Sidebar (200px)</card>
</split>
        </template>
      </ry-example>
      <ry-example title="split resizable" stacked>
        <template>
<split resizable style="height: 150px;">
  <card>Drag the handle to resize →</card>
  <card>Resizable sidebar (default 300px)</card>
</split>
        </template>
      </ry-example>
      <ry-example title="split resizable + persist" stacked>
        <template>
<split resizable persist="demo-panel" style="height: 150px; --ry-split-width: 250px;">
  <card>Width persists across page reloads. Double-click handle to reset.</card>
  <card>Persisted sidebar</card>
</split>
        </template>
        <script slot="js" type="text/plain">
const split = document.querySelector('ry-split[resizable]');

// Listen for resize
split.addEventListener('ry:resize', (e) => {
  console.log(e.detail.width);  // new width in px
});

// Keyboard: Arrow keys (±10px), Shift+Arrow (±50px)
// Home = min width, End = max width
// Double-click handle = reset to default
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>resizable</code></td><td>boolean</td><td>Enable drag-to-resize handle</td></tr>
          <tr><td><code>persist</code></td><td>string</td><td>localStorage key — saves width across reloads</td></tr>
          <tr><td><code>--ry-split-width</code></td><td>CSS length</td><td>Sidebar width (default 300px)</td></tr>
          <tr><td><code>--ry-split-min-width</code></td><td>CSS length</td><td>Min width during resize (default 100px)</td></tr>
          <tr><td><code>--ry-split-max-width</code></td><td>CSS length</td><td>Max width during resize (default 80% of container)</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Check List -->
    <ry-section>
      <h2>Check List</h2>
      <ry-example title="check-list">
        <template>
<ul class="ry-check-list">
  <li>Unlimited projects</li>
  <li>Priority support</li>
  <li>Advanced analytics</li>
  <li>Custom domains</li>
</ul>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Class</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>.ry-check-list</code></td><td>Green checkmark list (CSS-only, no JS)</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Accordion -->
    <ry-section>
      <h2>Accordion</h2>
      <ry-example title="accordion">
        <template>
<accordion>
  <accordion-item title="What is ry-ui?" open>
    A framework-agnostic, Light DOM component library.
  </accordion-item>
  <accordion-item title="Do I need a build step?">
    Nope! Just link the CSS and JS files.
  </accordion-item>
</accordion>
        </template>
        <script slot="js" type="text/plain">
const accordion = document.querySelector('ry-accordion');
const item = document.querySelector('ry-accordion-item');

// Listen for open/close
item.addEventListener('ry:toggle', (e) => {
  console.log(e.detail.open);  // true or false
});

// Open/close programmatically
item.open = true;   // Expand
item.open = false;  // Collapse

// Toggle
item.toggle();
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>title</code></td><td>string</td><td>Header text (on accordion-item)</td></tr>
          <tr><td><code>open</code></td><td>boolean</td><td>Initially expanded (on accordion-item)</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Tabs -->
    <ry-section>
      <h2>Tabs</h2>
      <ry-example title="tabs">
        <template>
<tabs>
  <tab title="Overview" active>
    <p>This is the overview tab content.</p>
  </tab>
  <tab title="Features">
    <p>This is the features tab content.</p>
  </tab>
</tabs>
        </template>
        <script slot="js" type="text/plain">
const tabs = document.querySelector('ry-tabs');

// Listen for tab changes
tabs.addEventListener('ry:change', (e) => {
  console.log(e.detail.index);  // 0, 1, 2...
  console.log(e.detail.title);  // "Overview"
});

// Switch tabs programmatically
tabs.activeIndex = 1;  // Switch to second tab
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>title</code></td><td>string</td><td>Tab label (on tab)</td></tr>
          <tr><td><code>active</code></td><td>boolean</td><td>Initially selected (on tab)</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Modal -->
    <ry-section>
      <h2>Modal</h2>
      <ry-example title="modal">
        <template>
<button modal="example-modal">Open Modal</button>

<modal id="example-modal" title="Modal Title">
  <p>Modal content with focus trapping.</p>
  <footer>
    <button variant="ghost" close>Cancel</button>
    <button>Confirm</button>
  </footer>
</modal>
        </template>
        <script slot="js" type="text/plain">
const modal = document.querySelector('ry-modal');

// Listen for open/close
modal.addEventListener('ry:open', () => {
  console.log('Modal opened');
});

modal.addEventListener('ry:close', () => {
  console.log('Modal closed');
});

// Open/close programmatically
modal.open();
modal.close();

// Check state
if (modal.state === 'open') { ... }
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>id</code></td><td>string</td><td>Modal identifier</td></tr>
          <tr><td><code>title</code></td><td>string</td><td>Header title</td></tr>
          <tr><td><code>close</code></td><td>boolean</td><td>Closes modal (on button)</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Dropdown -->
    <ry-section>
      <h2>Dropdown</h2>
      <ry-example title="dropdown">
        <template>
<dropdown>
  <button>Options ▾</button>
  <menu>
    <menu-item>Edit</menu-item>
    <menu-item>Duplicate</menu-item>
    <divider></divider>
    <menu-item>Delete</menu-item>
  </menu>
</dropdown>
        </template>
        <script slot="js" type="text/plain">
const dropdown = document.querySelector('ry-dropdown');

// Listen for menu item selection
dropdown.addEventListener('ry:select', (e) => {
  console.log(e.detail.value);  // "Edit", "Delete", etc.
});

// Open/close programmatically
dropdown.open();
dropdown.close();
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td>first child</td><td>—</td><td>First child element is the trigger</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Field -->
    <ry-section>
      <h2>Field</h2>
      <ry-example title="field">
        <template>
<stack>
  <field label="Email" hint="We'll never share your email">
    <input type="email" placeholder="you@example.com">
  </field>
  <field label="Password" error="Must be at least 8 characters">
    <input type="password" placeholder="Enter password">
  </field>
  <field label="Message">
    <textarea rows="3" placeholder="Your message..."></textarea>
  </field>
</stack>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>label</code></td><td>string</td><td>Field label text</td></tr>
          <tr><td><code>error</code></td><td>string</td><td>Error message (hides hint when set)</td></tr>
          <tr><td><code>hint</code></td><td>string</td><td>Helper text below input</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Checkbox -->
    <ry-section>
      <h2>Checkbox</h2>
      <ry-example title="checkbox">
        <template>
<stack gap="sm">
  <label>
    <input type="checkbox" checked> Accept terms and conditions
  </label>
  <label>
    <input type="checkbox"> Subscribe to newsletter
  </label>
  <label>
    <input type="checkbox" disabled> Disabled option
  </label>
</stack>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>checked</code></td><td>boolean</td><td>Initially checked</td></tr>
          <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Radio -->
    <ry-section>
      <h2>Radio</h2>
      <ry-example title="radio">
        <template>
<stack gap="sm">
  <label>
    <input type="radio" name="plan" value="free" checked> Free plan
  </label>
  <label>
    <input type="radio" name="plan" value="pro"> Pro plan
  </label>
  <label>
    <input type="radio" name="plan" value="enterprise"> Enterprise
  </label>
  <label>
    <input type="radio" name="plan" value="disabled" disabled> Disabled
  </label>
</stack>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>name</code></td><td>string</td><td>Groups radios together</td></tr>
          <tr><td><code>value</code></td><td>string</td><td>Value when selected</td></tr>
          <tr><td><code>checked</code></td><td>boolean</td><td>Initially selected</td></tr>
          <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Badge -->
    <ry-section>
      <h2>Badge</h2>
      <ry-example title="badge">
        <template>
<cluster>
  <badge>Default</badge>
  <badge variant="primary">Primary</badge>
  <badge variant="success">Success</badge>
  <badge variant="warning">Warning</badge>
  <badge variant="danger">Danger</badge>
  <badge variant="accent">Accent</badge>
  <badge style="--ry-badge-color: oklch(0.6 0.2 150)">Custom Color</badge>
</cluster>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>variant</code></td><td>default | primary | success | warning | danger | accent</td><td>Badge style</td></tr>
          <tr><td><code>--ry-badge-color</code></td><td>CSS color</td><td>Arbitrary background color via style</td></tr>
          <tr><td><code>--ry-badge-text</code></td><td>CSS color</td><td>Arbitrary text color via style</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Alert -->
    <ry-section>
      <h2>Alert</h2>
      <ry-example title="alert">
        <template>
<stack>
  <alert type="info" title="Info">
    This is an informational message.
  </alert>
  <alert type="success" title="Success">
    Operation completed successfully.
  </alert>
  <alert type="warning" title="Warning">
    Please review before continuing.
  </alert>
  <alert type="danger" title="Error">
    Something went wrong.
  </alert>
</stack>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>type</code></td><td>info | success | warning | danger</td><td>Alert style</td></tr>
          <tr><td><code>title</code></td><td>string</td><td>Alert heading</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Switch -->
    <ry-section>
      <h2>Switch</h2>
      <ry-example title="switch">
        <template>
<stack gap="sm">
  <switch name="notifications" checked>Enable notifications</switch>
  <switch name="darkMode">Dark mode</switch>
  <switch disabled>Disabled switch</switch>
</stack>
        </template>
        <script slot="js" type="text/plain">
const toggle = document.querySelector('ry-switch');

// Listen for changes
toggle.addEventListener('ry:change', (e) => {
  console.log(e.detail.checked);  // true or false
  console.log(e.detail.name);     // "notifications"
});

// Get/set programmatically
toggle.checked;           // true
toggle.checked = false;   // Turn off

// Form usage with hidden input
const hidden = document.querySelector('input[name="notifications"]');
toggle.addEventListener('ry:change', (e) => {
  hidden.value = e.detail.checked ? '1' : '0';
});
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>name</code></td><td>string</td><td>Form field name</td></tr>
          <tr><td><code>checked</code></td><td>boolean</td><td>Initially checked</td></tr>
          <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Slider -->
    <ry-section>
      <h2>Slider</h2>
      <ry-example title="slider" stacked>
        <template>
<stack gap="lg">
  <slider min="0" max="100" value="50"></slider>

  <slider min="0" max="100" start="25" end="75" range labeled></slider>

  <slider min="0" max="10" step="1" value="5" ticked labeled tooltip></slider>

  <stack gap="sm">
    <slider min="0" max="100" value="60" color="success"></slider>
    <slider min="0" max="100" value="60" color="warning"></slider>
    <slider min="0" max="100" value="60" color="danger"></slider>
    <slider min="0" max="100" value="60" color="info"></slider>
  </stack>

  <slider min="0" max="100" value="50" disabled></slider>
</stack>
        </template>
        <script slot="js" type="text/plain">
const slider = document.querySelector('ry-slider');

// Listen for changes (fires during drag)
slider.addEventListener('ry:input', (e) => {
  console.log(e.detail.value);  // 50 (single slider)
  // For range slider:
  // e.detail.start, e.detail.end
});

// Listen for final value (fires on mouseup)
slider.addEventListener('ry:change', (e) => {
  saveVolume(e.detail.value);
});

// Get/set programmatically
slider.value;         // 50
slider.value = 75;    // Update value

// Range slider
rangeSlider.start;    // 25
rangeSlider.end;      // 75
rangeSlider.start = 10;
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>min</code></td><td>number</td><td>Minimum value (default: 0)</td></tr>
          <tr><td><code>max</code></td><td>number</td><td>Maximum value (default: 100)</td></tr>
          <tr><td><code>step</code></td><td>number</td><td>Step increment, 0 for smooth (default: 1)</td></tr>
          <tr><td><code>value</code></td><td>number</td><td>Current value (single slider)</td></tr>
          <tr><td><code>start</code> / <code>end</code></td><td>number</td><td>Range values (with range attribute)</td></tr>
          <tr><td><code>range</code></td><td>boolean</td><td>Enable dual-handle range mode</td></tr>
          <tr><td><code>labeled</code></td><td>boolean</td><td>Show value labels</td></tr>
          <tr><td><code>ticked</code></td><td>boolean</td><td>Show tick marks</td></tr>
          <tr><td><code>tooltip</code></td><td>boolean</td><td>Show value on thumb hover</td></tr>
          <tr><td><code>vertical</code></td><td>boolean</td><td>Vertical orientation</td></tr>
          <tr><td><code>reversed</code></td><td>boolean</td><td>Reverse direction</td></tr>
          <tr><td><code>color</code></td><td>primary | secondary | success | warning | danger | info</td><td>Track color</td></tr>
          <tr><td><code>size</code></td><td>sm | lg</td><td>Size variant</td></tr>
          <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Knob -->
    <ry-section>
      <h2>Knob</h2>
      <ry-example title="knob" stacked>
        <template>
<stack gap="lg">
  <cluster>
    <knob min="0" max="100" value="50" label="Volume" description="Master output volume"></knob>
    <knob min="0" max="100" value="75" label="Pan" description="Left/right stereo position"></knob>
    <knob min="0" max="100" value="25" label="Reverb" description="Room reverb amount"></knob>
  </cluster>

  <cluster>
    <knob min="0" max="3" step="1" value="1" labels="Off,Low,Med,High" label="Mode"></knob>
    <knob min="0" max="4" step="1" value="2" labels="∿,△,▢,▲,◇" label="Wave"></knob>
  </cluster>

  <cluster>
    <knob value="60" color="success" label="Success"></knob>
    <knob value="60" color="warning" label="Warning"></knob>
    <knob value="60" color="danger" label="Danger"></knob>
    <knob value="60" color="secondary" label="Secondary"></knob>
  </cluster>

  <cluster>
    <knob size="sm" value="25" label="Small"></knob>
    <knob value="50" label="Default"></knob>
    <knob size="lg" value="75" label="Large"></knob>
  </cluster>

  <knob min="0" max="100" value="50" label="Disabled" disabled></knob>
</stack>
        </template>
        <script slot="js" type="text/plain">
const knob = document.querySelector('ry-knob');

// Listen for changes (fires during drag)
knob.addEventListener('ry:input', (e) => {
  console.log(e.detail.value);  // 0-100
  updateVolume(e.detail.value);
});

// Listen for final value (fires on release)
knob.addEventListener('ry:change', (e) => {
  saveSettings({ volume: e.detail.value });
});

// Get/set programmatically
knob.value;         // 50
knob.value = 75;    // Update value

// For discrete knobs with labels
modeKnob.value;     // 1 (index)
// Labels: "Off,Low,Med,High" → value 1 = "Low"
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>min</code></td><td>number</td><td>Minimum value (default: 0)</td></tr>
          <tr><td><code>max</code></td><td>number</td><td>Maximum value (default: 100)</td></tr>
          <tr><td><code>step</code></td><td>number</td><td>Step increment, 0 for smooth (default: 0)</td></tr>
          <tr><td><code>value</code></td><td>number</td><td>Current value</td></tr>
          <tr><td><code>label</code></td><td>string</td><td>Label text below knob</td></tr>
          <tr><td><code>labels</code></td><td>string</td><td>Comma-separated display labels for discrete values</td></tr>
          <tr><td><code>description</code></td><td>string</td><td>Tooltip text shown on hover</td></tr>
          <tr><td><code>color</code></td><td>primary | secondary | success | warning | danger</td><td>Track color</td></tr>
          <tr><td><code>size</code></td><td>sm | lg</td><td>Size variant</td></tr>
          <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Number Select -->
    <ry-section>
      <h2>Number Select</h2>
      <ry-example title="number-select" stacked>
        <template>
<ry-stack gap="xl">

  <p><strong>Arrow placement</strong></p>
  <ry-cluster gap="lg">
    <ry-number-select min="0" max="100" value="50"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" arrows="start"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" arrows="end"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" arrows="none"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" arrows="stacked"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" arrows="stacked-end" icons="chevron"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" arrows="stacked-start" icons="chevron"></ry-number-select>
  </ry-cluster>

  <p><strong>Icon styles</strong></p>
  <ry-cluster gap="lg">
    <ry-number-select min="0" max="100" value="50"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" icons="chevron"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" icons="arrow"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" icons="chevron" arrows="stacked"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" icons="arrow" arrows="stacked"></ry-number-select>
  </ry-cluster>

  <p><strong>Drag direction</strong> — stacked defaults to vertical</p>
  <ry-cluster gap="lg">
    <ry-number-select min="0" max="100" value="50"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" drag="y"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" drag="none"></ry-number-select>
  </ry-cluster>

  <p><strong>Prefix &amp; suffix</strong></p>
  <ry-cluster gap="lg">
    <ry-number-select min="0" max="1000" value="50" prefix="$"></ry-number-select>
    <ry-number-select min="0" max="360" value="90" suffix="°" icons="chevron" arrows="stacked"></ry-number-select>
    <ry-number-select min="0" max="100" value="75" suffix="%"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" prefix="$" suffix="USD" editable></ry-number-select>
  </ry-cluster>

  <p><strong>Step intervals</strong></p>
  <ry-cluster gap="lg">
    <ry-number-select min="0" max="100" value="50" step="1"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" step="5"></ry-number-select>
    <ry-number-select min="0" max="10" value="5" step="0.1"></ry-number-select>
    <ry-number-select min="0" max="1" value="0.5" step="0.01"></ry-number-select>
  </ry-cluster>

  <p><strong>Behaviors</strong></p>
  <ry-cluster gap="lg">
    <ry-number-select min="0" max="100" value="50" editable></ry-number-select>
    <ry-number-select min="0" max="7" value="3" wrap></ry-number-select>
    <ry-number-select min="0" max="100" value="50" disabled></ry-number-select>
  </ry-cluster>

  <p><strong>Sizes</strong></p>
  <ry-cluster gap="lg" style="align-items: center">
    <ry-number-select min="0" max="100" value="50" size="xs"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" size="sm"></ry-number-select>
    <ry-number-select min="0" max="100" value="50"></ry-number-select>
    <ry-number-select min="0" max="100" value="50" size="lg"></ry-number-select>
  </ry-cluster>

</ry-stack>
        </template>
        <script slot="js" type="text/plain">
const ns = document.querySelector('ry-number-select');

// Events — ry:input fires during drag, ry:change on commit
ns.addEventListener('ry:input', (e) => {
  console.log('input:', e.detail.value);
});
ns.addEventListener('ry:change', (e) => {
  console.log('change:', e.detail.value);
});

// Programmatic API
ns.value;           // 50
ns.value = 75;
ns.min = 10;
ns.step = 5;
ns.drag = 'y';      // switch drag direction
ns.disabled = true;
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>min</code></td><td>number</td><td>Minimum value (default: 0)</td></tr>
          <tr><td><code>max</code></td><td>number</td><td>Maximum value (default: 100)</td></tr>
          <tr><td><code>step</code></td><td>number</td><td>Step increment (default: 1)</td></tr>
          <tr><td><code>value</code></td><td>number</td><td>Current value (default: 0)</td></tr>
          <tr><td><code>arrows</code></td><td>both | start | end | stacked | none</td><td>Button placement (default: both)</td></tr>
          <tr><td><code>icons</code></td><td>plus-minus | chevron | arrow</td><td>Button icon style (default: plus-minus)</td></tr>
          <tr><td><code>drag</code></td><td>x | y | none</td><td>Drag direction (default: x, stacked defaults to y)</td></tr>
          <tr><td><code>prefix</code></td><td>string</td><td>Text before value (e.g. "$")</td></tr>
          <tr><td><code>suffix</code></td><td>string</td><td>Text after value (e.g. "°", "%")</td></tr>
          <tr><td><code>editable</code></td><td>boolean</td><td>Allow direct typing on click</td></tr>
          <tr><td><code>wrap</code></td><td>boolean</td><td>Wrap around min/max boundaries</td></tr>
          <tr><td><code>size</code></td><td>xs | sm | lg</td><td>Size variant</td></tr>
          <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Color Picker -->
    <ry-section>
      <h2>Color Picker</h2>
      <ry-example title="color-picker" stacked>
        <template>
<stack gap="lg">
  <p><strong>Dropdown mode (default)</strong> - click to open:</p>
  <cluster>
    <color-picker value="#3b82f6"></color-picker>
    <color-picker value="#22c55e"></color-picker>
    <color-picker value="#8b5cf6"></color-picker>
    <color-picker value="#ef4444" opacity></color-picker>
  </cluster>

  <p><strong>With swatches:</strong></p>
  <color-picker
    value="#3b82f6"
    swatches="#ef4444;#f59e0b;#22c55e;#3b82f6;#8b5cf6;#ec4899;#1e293b;#ffffff"
  ></color-picker>

  <p><strong>Inline mode</strong> - always visible:</p>
  <color-picker value="#3b82f6" inline></color-picker>

  <p><strong>With opacity + swatches (inline):</strong></p>
  <color-picker
    value="rgba(59, 130, 246, 0.7)"
    format="rgb"
    opacity
    inline
    swatches="#ef4444;#f59e0b;#22c55e;#3b82f6;#8b5cf6;#ec4899"
  ></color-picker>

  <cluster>
    <color-picker value="#64748b" disabled></color-picker>
    <span style="color: var(--ry-color-text-muted)">Disabled</span>
  </cluster>
</stack>
        </template>
        <script slot="js" type="text/plain">
// Get the element
const picker = document.querySelector('ry-color-picker');

// Listen for changes (fires during drag)
picker.addEventListener('ry:input', (e) => {
  console.log(e.detail.value);  // "#3b82f6"
  console.log(e.detail.rgb);    // { r: 59, g: 130, b: 246 }
  console.log(e.detail.hsv);    // { h: 217, s: 76, v: 96 }
});

// Listen for final value (fires on mouseup/blur)
picker.addEventListener('ry:change', (e) => {
  saveColor(e.detail.value);
});

// Get/set programmatically
picker.value;                    // "#3b82f6"
picker.value = '#ff0000';        // Set new color
picker.setColor('hsl(200, 100%, 50%)');  // Accepts any format

// Access color in different formats
picker.rgb;   // { r: 59, g: 130, b: 246 }
picker.hsl;   // { h: 217, s: 89, l: 60 }
picker.hsv;   // { h: 217, s: 76, v: 96 }

// Form submission
form.addEventListener('submit', (e) => {
  const color = picker.value;
  fetch('/api/save', { body: JSON.stringify({ color }) });
});
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>value</code></td><td>color string</td><td>Initial color (hex, rgb, hsl)</td></tr>
          <tr><td><code>format</code></td><td>hex | rgb | hsl</td><td>Output format (default: hex)</td></tr>
          <tr><td><code>inline</code></td><td>boolean</td><td>Always show picker (no dropdown)</td></tr>
          <tr><td><code>opacity</code></td><td>boolean</td><td>Enable alpha channel slider</td></tr>
          <tr><td><code>swatches</code></td><td>string</td><td>Preset colors separated by semicolons</td></tr>
          <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Color Input -->
    <ry-section>
      <h2>Color Input</h2>
      <ry-example title="color-input" stacked>
        <template>
<stack gap="lg">
  <p>Input-style color picker with editable hex value:</p>
  <cluster>
    <color-input value="#3b82f6"></color-input>
    <color-input value="#22c55e"></color-input>
    <color-input value="#ef4444"></color-input>
  </cluster>

  <p>With opacity support:</p>
  <color-input value="rgba(139, 92, 246, 0.8)" opacity></color-input>

  <cluster>
    <color-input value="#64748b" disabled></color-input>
    <span style="color: var(--ry-color-text-muted)">Disabled</span>
  </cluster>
</stack>
        </template>
        <script slot="js" type="text/plain">
const input = document.querySelector('ry-color-input');

// Listen for changes
input.addEventListener('ry:change', (e) => {
  console.log(e.detail.value);  // "#3b82f6"
});

// Get/set programmatically
input.value;           // "#3b82f6"
input.value = '#ff0000';

// Works great in forms
const hidden = document.querySelector('input[name="brandColor"]');
input.addEventListener('ry:change', (e) => {
  hidden.value = e.detail.value;
});
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>value</code></td><td>color string</td><td>Initial color (hex, rgb, hsl)</td></tr>
          <tr><td><code>format</code></td><td>hex | rgb | hsl</td><td>Output format (default: hex)</td></tr>
          <tr><td><code>opacity</code></td><td>boolean</td><td>Enable alpha channel slider</td></tr>
          <tr><td><code>placeholder</code></td><td>string</td><td>Placeholder text for input</td></tr>
          <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Gradient Picker -->
    <ry-section>
      <h2>Gradient Picker</h2>
      <ry-example title="gradient-picker" stacked>
        <template>
<stack gap="lg">
  <p><strong>Linear gradient</strong> (default):</p>
  <gradient-picker value="linear-gradient(90deg, #3b82f6 0%, #8b5cf6 100%)"></gradient-picker>

  <p><strong>Multi-stop rainbow with CSS output:</strong></p>
  <gradient-picker value="linear-gradient(90deg, #ef4444 0%, #f59e0b 25%, #22c55e 50%, #3b82f6 75%, #8b5cf6 100%)" output></gradient-picker>

  <p><strong>Radial gradient:</strong></p>
  <gradient-picker value="radial-gradient(circle, #fbbf24 0%, #f97316 50%, #dc2626 100%)"></gradient-picker>
</stack>
        </template>
        <script slot="js" type="text/plain">
const picker = document.querySelector('ry-gradient-picker');

// Listen for changes
picker.addEventListener('ry:input', (e) => {
  console.log(e.detail.value);  // CSS gradient string
  console.log(e.detail.stops);  // [{ id, color, position }, ...]
  console.log(e.detail.type);   // "linear" | "radial"
  console.log(e.detail.angle);  // 90 (degrees, linear only)
});

picker.addEventListener('ry:change', (e) => {
  applyGradient(e.detail.value);
});

// Get/set programmatically
picker.value;           // "linear-gradient(90deg, ...)"
picker.value = 'radial-gradient(circle, #ff0000 0%, #0000ff 100%)';
picker.type;            // "linear" | "radial"
picker.angle;           // 0-360
picker.stops;           // [{ id, color, position }, ...]

// Add/remove stops
picker.addStop('#ff00ff', 50);
picker.removeStop('stop-2');
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>value</code></td><td>CSS gradient string</td><td>Initial gradient value</td></tr>
          <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Toggle Button -->
    <ry-section>
      <h2>Toggle Button</h2>
      <ry-example title="toggle-button" stacked>
        <template>
<stack gap="lg">
  <grid cols="3">
    <toggle-button name="plan" value="bronze" block>
      <strong>Bronze</strong><br>
      <small>Standard Network</small><br>
      <big><b>$85</b>/month</big><br>
      <small>Deductible: $6,000 · Coverage: 60%</small><br>
      <small>✓ Preventive care ✓ Generic Rx ✓ Telehealth</small>
    </toggle-button>
    <toggle-button name="plan" value="silver" block>
      <strong>Silver</strong><br>
      <small>Enhanced Network</small><br>
      <big><b>$145</b>/month</big><br>
      <small>Deductible: $3,000 · Coverage: 80%</small><br>
      <small>✓ Lower deductible ✓ Specialists ✓ Mental health</small>
    </toggle-button>
    <toggle-button name="plan" value="gold" block>
      <strong>Gold</strong><br>
      <small>Premium Network</small><br>
      <big><b>$220</b>/month</big><br>
      <small>Deductible: $1,000 · Coverage: 90%</small><br>
      <small>✓ Lowest out-of-pocket ✓ Full Rx ✓ No referrals</small>
    </toggle-button>
  </grid>

  <cluster>
    <toggle-button name="coverage" value="medical" pressed>🏥 Medical</toggle-button>
    <toggle-button name="coverage" value="dental">🦷 Dental</toggle-button>
    <toggle-button name="coverage" value="vision">👁 Vision</toggle-button>
  </cluster>

  <stack>
    <grid cols="2">
      <toggle-button name="selection" value="ppo200" block>
        <strong>Blue PPO $200</strong><br>
        <small>Employee Only · $200 Deductible</small><br>
        <b>$171.28</b>/month
      </toggle-button>
      <toggle-button name="selection" value="ppo500" block>
        <strong>Blue PPO $500</strong><br>
        <small>Employee Only · $500 Deductible</small><br>
        <b>$142.53</b>/month
      </toggle-button>
    </grid>
    <toggle-button name="selection" value="waive" pressed>Waive $0/month</toggle-button>
  </stack>
</stack>
        </template>
        <script slot="js" type="text/plain">
const buttons = document.querySelectorAll('ry-toggle-button[name="plan"]');

// Listen for changes on each button
buttons.forEach(btn => {
  btn.addEventListener('ry:change', (e) => {
    console.log(e.detail.pressed);  // true or false
    console.log(e.detail.value);    // "bronze", "silver", etc.
  });
});

// Get selected value from a group
function getSelectedPlan() {
  const pressed = document.querySelector(
    'ry-toggle-button[name="plan"][pressed]'
  );
  return pressed?.value;  // "gold"
}

// Set programmatically
button.pressed = true;

// Form submission
form.addEventListener('submit', (e) => {
  const plan = getSelectedPlan();
  fetch('/api/enroll', { body: JSON.stringify({ plan }) });
});
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>name</code></td><td>string</td><td>Groups buttons (same name = only one pressed)</td></tr>
          <tr><td><code>value</code></td><td>string</td><td>Value when selected</td></tr>
          <tr><td><code>pressed</code></td><td>boolean</td><td>Currently pressed</td></tr>
          <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr>
          <tr><td><code>size</code></td><td>sm | md | lg</td><td>Button size</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Tooltip -->
    <ry-section>
      <h2>Tooltip</h2>
      <ry-example title="tooltip">
        <template>
<cluster>
  <tooltip content="Top tooltip" position="top">
    <button>Top</button>
  </tooltip>
  <tooltip content="Bottom tooltip" position="bottom">
    <button>Bottom</button>
  </tooltip>
  <tooltip content="Left tooltip" position="left">
    <button>Left</button>
  </tooltip>
  <tooltip content="Right tooltip" position="right">
    <button>Right</button>
  </tooltip>
</cluster>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>content</code></td><td>string</td><td>Tooltip text</td></tr>
          <tr><td><code>position</code></td><td>top | bottom | left | right</td><td>Tooltip position</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Drawer -->
    <ry-section>
      <h2>Drawer</h2>
      <ry-example title="drawer">
        <template>
<button drawer="example-drawer">Open Drawer</button>

<drawer id="example-drawer" side="right">
  <h3>Drawer Title</h3>
  <p>Drawer content here.</p>
</drawer>
        </template>
        <script slot="js" type="text/plain">
const drawer = document.querySelector('ry-drawer');

// Listen for open/close
drawer.addEventListener('ry:open', () => {
  console.log('Drawer opened');
});

drawer.addEventListener('ry:close', () => {
  console.log('Drawer closed');
});

// Open/close programmatically
drawer.open();
drawer.close();

// Check state
if (drawer.state === 'open') { ... }
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>id</code></td><td>string</td><td>Drawer identifier</td></tr>
          <tr><td><code>side</code></td><td>left | right | bottom</td><td>Slide direction</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Toast -->
    <ry-section>
      <h2>Toast</h2>
      <ry-example title="toast">
        <template>
<cluster>
  <button onclick="RyToast.success('Saved!')">Success</button>
  <button onclick="RyToast.error('Failed!')">Error</button>
  <button onclick="RyToast.warning('Warning!')">Warning</button>
  <button onclick="RyToast.info('Info!')">Info</button>
</cluster>
        </template>
        <script slot="js" type="text/plain">
// Show toasts (global API)
RyToast.success('Changes saved!');
RyToast.error('Something went wrong');
RyToast.warning('Please review');
RyToast.info('New update available');

// With options
RyToast.success('Saved!', {
  duration: 5000,  // ms (default: 3000)
});

// Common patterns
async function saveData() {
  try {
    await api.save(data);
    RyToast.success('Saved!');
  } catch (err) {
    RyToast.error(err.message);
  }
}
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Method</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>RyToast.success(msg)</code></td><td>Show success toast</td></tr>
          <tr><td><code>RyToast.error(msg)</code></td><td>Show error toast</td></tr>
          <tr><td><code>RyToast.warning(msg)</code></td><td>Show warning toast</td></tr>
          <tr><td><code>RyToast.info(msg)</code></td><td>Show info toast</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Select -->
    <ry-section>
      <h2>Select</h2>
      <ry-example title="select">
        <template>
<select placeholder="Choose a country" name="country">
  <option value="us">United States</option>
  <option value="uk">United Kingdom</option>
  <option value="ca">Canada</option>
  <option value="au">Australia</option>
</select>
        </template>
        <script slot="js" type="text/plain">
const select = document.querySelector('ry-select');

// Listen for selection changes
select.addEventListener('ry:change', (e) => {
  console.log(e.detail.value);  // "us"
  console.log(e.detail.label);  // "United States"
});

// Get/set programmatically
select.value;           // "us"
select.value = 'uk';    // Select United Kingdom

// Open/close the dropdown
select.open();
select.close();

// Form integration
const formData = new FormData(form);
formData.get('country');  // "us"
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>placeholder</code></td><td>string</td><td>Placeholder text</td></tr>
          <tr><td><code>name</code></td><td>string</td><td>Form field name</td></tr>
          <tr><td><code>disabled</code></td><td>boolean</td><td>Disable select (on option)</td></tr>
          <tr><td><code>multiple</code></td><td>boolean</td><td>Enable multi-select with tags</td></tr>
          <tr><td><code>clearable</code></td><td>boolean</td><td>Show clear button (multi)</td></tr>
          <tr><td><code>max-selections</code></td><td>number</td><td>Max selections (multi)</td></tr>
        </tbody>
      </table>

      <h3 style="margin-top: var(--ry-space-6);">Multi-Select</h3>
      <ry-example title="select-multi" stacked>
        <template>
<select multiple clearable placeholder="Choose countries...">
  <option value="us">United States</option>
  <option value="uk">United Kingdom</option>
  <option value="ca">Canada</option>
  <option value="au">Australia</option>
  <option value="de">Germany</option>
  <option value="fr">France</option>
</select>
        </template>
      </ry-example>
    </ry-section>

    <!-- Combobox -->
    <ry-section>
      <h2>Combobox</h2>
      <ry-example title="combobox">
        <template>
<combobox placeholder="Search countries..." name="country">
  <option value="us">United States</option>
  <option value="uk">United Kingdom</option>
  <option value="ca">Canada</option>
  <option value="au">Australia</option>
  <option value="de">Germany</option>
  <option value="fr">France</option>
  <option value="jp">Japan</option>
  <option value="br">Brazil</option>
</combobox>
        </template>
        <script slot="js" type="text/plain">
const combobox = document.querySelector('ry-combobox');

// Listen for selection
combobox.addEventListener('ry:change', (e) => {
  console.log(e.detail.value);  // "us"
  console.log(e.detail.label);  // "United States"
});

// Get/set programmatically
combobox.value;           // "us"
combobox.value = 'uk';    // Select United Kingdom

// Form integration
const formData = new FormData(form);
formData.get('country');  // "us"
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>placeholder</code></td><td>string</td><td>Placeholder text for input</td></tr>
          <tr><td><code>name</code></td><td>string</td><td>Form field name</td></tr>
          <tr><td><code>value</code></td><td>string</td><td>Currently selected value</td></tr>
          <tr><td><code>disabled</code></td><td>boolean</td><td>Disable combobox</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Code -->
    <ry-section>
      <h2>Code</h2>
      <ry-example title="ry-code">
        <template>
<ry-code language="js" title="app.js">
const greeting = "Hello!";
console.log(greeting);
</ry-code>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>language</code></td><td>js | css | html | json</td><td>Syntax highlighting</td></tr>
          <tr><td><code>title</code></td><td>string</td><td>Header title</td></tr>
          <tr><td><code>line-numbers</code></td><td>boolean</td><td>Show line numbers</td></tr>
        </tbody>
      </table>
      <p><em>Note: Use <code>&lt;ry-code&gt;</code> with prefix to avoid conflict with native <code>&lt;code&gt;</code> tag.</em></p>
    </ry-section>

    <!-- Table -->
    <ry-section>
      <h2>Table</h2>
      <ry-example title="table">
        <template>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Role</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alice Johnson</td>
      <td>Engineer</td>
      <td>Active</td>
    </tr>
    <tr>
      <td>Bob Smith</td>
      <td>Designer</td>
      <td>Active</td>
    </tr>
    <tr>
      <td>Carol White</td>
      <td>Manager</td>
      <td>Away</td>
    </tr>
  </tbody>
</table>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>data-bordered</code></td><td>boolean</td><td>Add borders to all cells</td></tr>
          <tr><td><code>data-striped</code></td><td>boolean</td><td>Alternate row backgrounds</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Tree -->
    <ry-section>
      <h2>Tree</h2>
      <ry-example title="tree" stacked>
        <template>
<tree sortable>
  <tree-item label="src" open>
    <tree-item label="app" open>
      <tree-item label="layout.tsx"></tree-item>
      <tree-item label="page.tsx"></tree-item>
    </tree-item>
    <tree-item label="components" open>
      <tree-item label="ui" open>
        <tree-item label="button.tsx" selected></tree-item>
      </tree-item>
      <tree-item label="header.tsx"></tree-item>
      <tree-item label="footer.tsx"></tree-item>
    </tree-item>
    <tree-item label="lib" open>
      <tree-item label="utils.ts"></tree-item>
    </tree-item>
  </tree-item>
</tree>
        </template>
        <script slot="js" type="text/plain">
const tree = document.querySelector('ry-tree');

// Listen for file selection
tree.addEventListener('ry:select', (e) => {
  console.log(e.detail.label);  // "button.tsx"
});

// Listen for folder toggle
tree.addEventListener('ry:toggle', (e) => {
  console.log(e.detail.label, e.detail.open);
});

// Listen for drag-and-drop moves
tree.addEventListener('ry:move', (e) => {
  console.log(`Moved "${e.detail.item}" ${e.detail.position} "${e.detail.target}"`);

  // Get the full new structure as JSON
  const state = tree.toJSON();
  console.log(JSON.stringify(state, null, 2));
});

// Serialize current state
tree.toJSON();   // [{ label: "src", open: true, children: [...] }]
tree.value;      // Same thing

// Create tree from JSON data
const copy = RyTree.from(tree.toJSON());
document.body.appendChild(copy);
        </script>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>label</code></td><td>string</td><td>Item label (on tree-item)</td></tr>
          <tr><td><code>open</code></td><td>boolean</td><td>Initially expanded folder (on tree-item with children)</td></tr>
          <tr><td><code>selected</code></td><td>boolean</td><td>Initially selected file (on leaf tree-item)</td></tr>
          <tr><td><code>sortable</code></td><td>boolean</td><td>Enable drag-and-drop rearranging (on tree)</td></tr>
          <tr><td><code>no-animate</code></td><td>boolean</td><td>Disable expand/collapse animation (on tree)</td></tr>
          <tr><td><code>--ry-tree-duration</code></td><td>CSS time</td><td>Animation duration (default: 150ms)</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Icon -->
    <ry-section>
      <h2>Icon</h2>
      <ry-example title="icon">
        <template>
<cluster>
  <icon name="heart"></icon>
  <icon name="star"></icon>
  <icon name="search"></icon>
  <icon name="settings"></icon>
  <icon name="user"></icon>
  <icon name="check"></icon>
</cluster>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>name</code></td><td>close | check | chevron-* | copy | sun | moon | info | warning | error | success | search | menu | plus | minus | settings | user | heart | star | trash | edit | external-link | download | upload</td><td>Icon name</td></tr>
          <tr><td><code>size</code></td><td>number</td><td>Size in pixels (default: 24)</td></tr>
          <tr><td><code>label</code></td><td>string</td><td>Accessible label</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Tag -->
    <ry-section>
      <h2>Tag</h2>
      <ry-example title="tag" stacked>
        <template>
<cluster>
  <tag>Default</tag>
  <tag variant="primary">Primary</tag>
  <tag variant="success">Success</tag>
  <tag variant="warning">Warning</tag>
  <tag variant="danger">Danger</tag>
  <tag removable>Removable</tag>
  <tag variant="primary" removable>Remove me</tag>
  <tag size="sm">Small</tag>
  <tag size="lg">Large</tag>
</cluster>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>variant</code></td><td>default | primary | success | warning | danger</td><td>Color variant</td></tr>
          <tr><td><code>size</code></td><td>sm | md | lg</td><td>Size</td></tr>
          <tr><td><code>removable</code></td><td>boolean</td><td>Show close button</td></tr>
          <tr><td><code>data-value</code></td><td>string</td><td>Value included in ry:remove event</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Tag Input -->
    <ry-section>
      <h2>Tag Input</h2>
      <ry-example title="tag-input" stacked>
        <template>
<tag-input placeholder="Add tags (comma separated)..." value="JavaScript,TypeScript,CSS"></tag-input>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>placeholder</code></td><td>string</td><td>Input placeholder</td></tr>
          <tr><td><code>delimiter</code></td><td>string</td><td>Delimiter character (default ",")</td></tr>
          <tr><td><code>max-tags</code></td><td>number</td><td>Max number of tags</td></tr>
          <tr><td><code>value</code></td><td>string</td><td>Initial comma-separated values</td></tr>
          <tr><td><code>name</code></td><td>string</td><td>Form field name</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Hero -->
    <ry-section>
      <h2>Hero</h2>
      <ry-example title="hero" stacked>
        <template>
<hero align="center">
  <h1>Build apps faster</h1>
  <p>Framework-agnostic, Light DOM web components. CSS is the source of truth.</p>
  <cluster>
    <button>Get Started</button>
    <button variant="outline">Learn More</button>
  </cluster>
</hero>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>align</code></td><td>center | left</td><td>Text alignment</td></tr>
          <tr><td><code>size</code></td><td>sm | md | lg</td><td>Padding size</td></tr>
          <tr><td><code>full-bleed</code></td><td>boolean</td><td>Remove max-width</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Stat -->
    <ry-section>
      <h2>Stat</h2>
      <ry-example title="stat">
        <template>
<grid cols="3">
  <stat trend="up" value="2,847" label="Active Users"></stat>
  <stat trend="down" value="1.2%" label="Bounce Rate"></stat>
  <stat value="$12,340" label="Revenue"></stat>
</grid>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>trend</code></td><td>up | down</td><td>Trend arrow direction</td></tr>
          <tr><td><code>size</code></td><td>sm | md | lg</td><td>Value text size</td></tr>
          <tr><td><code>align</code></td><td>left | center</td><td>Alignment</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Feature Grid -->
    <ry-section>
      <h2>Feature Grid</h2>
      <ry-example title="feature-grid" stacked>
        <template>
<feature-grid cols="3">
  <feature icon="settings" align="center">
    <h3>Easy Setup</h3>
    <p>One line of code to get started. No build step required.</p>
  </feature>
  <feature icon="heart" align="center">
    <h3>Developer Friendly</h3>
    <p>Clean API, great docs, and TypeScript support.</p>
  </feature>
  <feature icon="star" align="center">
    <h3>Lightweight</h3>
    <p>Under 10KB gzipped. No dependencies.</p>
  </feature>
</feature-grid>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>cols</code> (grid)</td><td>2 | 3 | 4</td><td>Number of columns</td></tr>
          <tr><td><code>icon</code> (feature)</td><td>icon name</td><td>Icon from registry</td></tr>
          <tr><td><code>align</code> (feature)</td><td>left | center</td><td>Content alignment</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Pricing -->
    <ry-section>
      <h2>Pricing</h2>
      <ry-example title="pricing" stacked>
        <template>
<pricing>
  <pricing-card title="Free" price="$0/mo">
    <ul class="ry-check-list">
      <li>5 projects</li>
      <li>Community support</li>
      <li>Basic analytics</li>
    </ul>
    <button variant="outline">Get Started</button>
  </pricing-card>
  <pricing-card featured title="Pro" price="$29/mo">
    <ul class="ry-check-list">
      <li>Unlimited projects</li>
      <li>Priority support</li>
      <li>Advanced analytics</li>
    </ul>
    <button>Choose Pro</button>
  </pricing-card>
  <pricing-card title="Enterprise" price="$99/mo">
    <ul class="ry-check-list">
      <li>Everything in Pro</li>
      <li>SLA guarantee</li>
      <li>Custom integrations</li>
    </ul>
    <button variant="outline">Contact Us</button>
  </pricing-card>
</pricing>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>featured</code></td><td>boolean</td><td>Highlight this card</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Testimonial -->
    <ry-section>
      <h2>Testimonial</h2>
      <ry-example title="testimonial">
        <template>
<ry>
<grid cols="3">
  <testimonial stars="5" avatar="https://i.pravatar.cc/128?img=1" name="Sarah Chen" role="CTO, Acme Corp">
    This library changed how we build UIs. Everything just works.
  </testimonial>
  <testimonial stars="5" avatar="https://i.pravatar.cc/128?img=3" name="Marcus Johnson" role="Lead Engineer, StartupCo">
    Clean, fast, and framework-agnostic. Exactly what we needed.
  </testimonial>
  <testimonial stars="4" avatar="https://i.pravatar.cc/128?img=5" name="Emily Park" role="Product Manager, BigCo">
    We shipped our entire dashboard in half the time.
  </testimonial>
</grid>
</ry>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>stars</code></td><td>1–5</td><td>Star rating display</td></tr>
          <tr><td><code>avatar</code></td><td>image URL</td><td>Avatar photo</td></tr>
          <tr><td><code>name</code></td><td>text</td><td>Person's name</td></tr>
          <tr><td><code>role</code></td><td>text</td><td>Title / company</td></tr>
        </tbody>
      </table>
    </ry-section>

    <!-- Carousel -->
    <ry-section>
      <h2>Carousel</h2>
      <ry-example title="carousel" stacked>
        <template>
<carousel arrows dots loop>
  <div style="padding: var(--ry-space-8); text-align: center; background: var(--ry-color-bg-subtle); border-radius: var(--ry-radius-lg);">
    <h3>Slide 1</h3>
    <p>First slide content</p>
  </div>
  <div style="padding: var(--ry-space-8); text-align: center; background: var(--ry-color-bg-muted); border-radius: var(--ry-radius-lg);">
    <h3>Slide 2</h3>
    <p>Second slide content</p>
  </div>
  <div style="padding: var(--ry-space-8); text-align: center; background: var(--ry-color-bg-subtle); border-radius: var(--ry-radius-lg);">
    <h3>Slide 3</h3>
    <p>Third slide content</p>
  </div>
</carousel>
        </template>
      </ry-example>
      <table>
        <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead>
        <tbody>
          <tr><td><code>arrows</code></td><td>boolean</td><td>Show prev/next arrows</td></tr>
          <tr><td><code>dots</code></td><td>boolean</td><td>Show dot indicators</td></tr>
          <tr><td><code>loop</code></td><td>boolean</td><td>Loop back to start</td></tr>
          <tr><td><code>autoplay</code></td><td>boolean</td><td>Auto-advance slides</td></tr>
          <tr><td><code>interval</code></td><td>number</td><td>Autoplay interval in ms (default 5000)</td></tr>
          <tr><td><code>pause-on-hover</code></td><td>boolean</td><td>Pause autoplay on hover</td></tr>
        </tbody>
      </table>
    </ry-section>

  </ry-main>

  <ry-footer>
    <p>ry-ui &bull; Framework-agnostic Light DOM components</p>
  </ry-footer>

</ry-page>