Theme

Advanced Components

Clipboard

Hello from dryui!

Marquee

dryui — 73 components, zero dependencies, native browser APIs, Svelte 5 runes   •  

QR Code

Code Block

svelte
1import { Button, Card } from '@dryui/ui';
2
3function App() {
4  let count = $state(0);
5
6  return (
7    <Card.Root>
8      <Card.Content>
9        <Button onclick={() => count++}>
10          Count: {count}
11        </Button>
12      </Card.Content>
13    </Card.Root>
14  );
15}

Markdown Renderer

Hello World

This is a markdown renderer with italic and inline code.

  • Item one
  • Item two
  • Item three

A blockquote with some wisdom.

js
const greeting = "Hello, dryui!";
console.log(greeting);

Visit dryui docs

VirtualList (10,000 items)

Item #1 This is virtual list item number 1
Item #2 This is virtual list item number 2
Item #3 This is virtual list item number 3
Item #4 This is virtual list item number 4
Item #5 This is virtual list item number 5

Infinite Scroll

Loaded item 1
Loaded item 2
Loaded item 3
Loaded item 4
Loaded item 5
Loaded item 6
Loaded item 7
Loaded item 8
Loaded item 9
Loaded item 10
Loaded item 11
Loaded item 12
Loaded item 13
Loaded item 14
Loaded item 15
Loaded item 16
Loaded item 17
Loaded item 18
Loaded item 19
Loaded item 20

DataGrid

Alice Engineer Frontend $120,000
Bob Designer Design $95,000
Carol Manager Engineering $150,000
Dave Engineer Backend $130,000
Eve Analyst Data $110,000

Drag and Drop

First item
Second item
Third item
Fourth item
Fifth item

Transfer

0/6
0/0

Rich Text Editor

Tour demonstrates step-by-step guidance

Source

This page is rendered from the real demo file shown below.

svelte
<script lang="ts">
  import {
    Clipboard,
    ScrollToTop,
    FloatButton,
    Marquee,
    QRCode,
    CodeBlock,
    MarkdownRenderer,
    VirtualList,
    InfiniteScroll,
    DataGrid,
    DragAndDrop,
    Transfer,
    RichTextEditor,
    Tour,
    VisuallyHidden,
    Button,
  } from '@dryui/ui';
  import type { TourStep } from '@dryui/ui';
  import type { TransferItem } from '@dryui/primitives';

  // VirtualList data
  const virtualItems = Array.from({ length: 10000 }, (_, i) => ({
    id: i,
    label: `Item #${i + 1}`,
    description: `This is virtual list item number ${i + 1}`,
  }));

  // InfiniteScroll state
  let infiniteItems = $state(Array.from({ length: 20 }, (_, i) => `Loaded item ${i + 1}`));
  let hasMore = $state(true);
  let loading = $state(false);

  function loadMore() {
    loading = true;
    setTimeout(() => {
      const start = infiniteItems.length;
      const newItems = Array.from({ length: 10 }, (_, i) => `Loaded item ${start + i + 1}`);
      infiniteItems = [...infiniteItems, ...newItems];
      loading = false;
      if (infiniteItems.length >= 60) hasMore = false;
    }, 800);
  }

  // DataGrid data
  type GridRow = {
    name: string;
    role: string;
    department: string;
    salary: number;
  };

  const gridData: GridRow[] = [
    { name: 'Alice', role: 'Engineer', department: 'Frontend', salary: 120000 },
    { name: 'Bob', role: 'Designer', department: 'Design', salary: 95000 },
    { name: 'Carol', role: 'Manager', department: 'Engineering', salary: 150000 },
    { name: 'Dave', role: 'Engineer', department: 'Backend', salary: 130000 },
    { name: 'Eve', role: 'Analyst', department: 'Data', salary: 110000 },
    { name: 'Frank', role: 'Designer', department: 'Design', salary: 98000 },
    { name: 'Grace', role: 'Engineer', department: 'Frontend', salary: 125000 },
    { name: 'Hank', role: 'DevOps', department: 'Infrastructure', salary: 135000 },
  ];

  // DragAndDrop state
  let dndItems = $state(['First item', 'Second item', 'Third item', 'Fourth item', 'Fifth item']);

  // Transfer state
  let sourceItems: TransferItem[] = $state([
    { key: '1', label: 'JavaScript' },
    { key: '2', label: 'TypeScript' },
    { key: '3', label: 'Python' },
    { key: '4', label: 'Rust' },
    { key: '5', label: 'Go' },
    { key: '6', label: 'Java' },
  ]);
  let targetItems: TransferItem[] = $state([]);

  // RichTextEditor state
  let editorValue = $state('<p>Edit this <strong>rich text</strong> content...</p>');

  // Tour state
  let tourActive = $state(false);
  const tourSteps: TourStep[] = [
    { target: '[data-tour="qr"]', title: 'QR Code', content: 'Generate QR codes from any text.', placement: 'bottom' },
    { target: '[data-tour="code"]', title: 'Code Block', content: 'Display code with syntax highlighting.', placement: 'bottom' },
    { target: '[data-tour="markdown"]', title: 'Markdown', content: 'Render markdown as styled HTML.', placement: 'top' },
  ];

  // Markdown content
  const markdownContent = `# Hello World

This is a **markdown** renderer with *italic* and \`inline code\`.

- Item one
- Item two
- Item three

> A blockquote with some wisdom.

\`\`\`js
const greeting = "Hello, dryui!";
console.log(greeting);
\`\`\`

[Visit dryui docs](https://example.com)`;

  const sampleCode = `import { Button, Card } from '@dryui/ui';

function App() {
  let count = $state(0);

  return (
    <Card.Root>
      <Card.Content>
        <Button onclick={() => count++}>
          Count: {count}
        </Button>
      </Card.Content>
    </Card.Root>
  );
}`;
</script>

<h2>Advanced Components</h2>

<!-- Clipboard -->
<h3>Clipboard</h3>
<Clipboard value="Hello from dryui!">
  {#snippet children({ copied, copy })}
    <div class="row">
      <code>Hello from dryui!</code>
      <Button size="sm" variant="outline" onclick={copy}>
        {copied ? 'Copied!' : 'Copy'}
      </Button>
    </div>
  {/snippet}
</Clipboard>

<!-- Marquee -->
<h3>Marquee</h3>
<Marquee speed={60} pauseOnHover>
  <span class="marquee-content">
    dryui — 73 components, zero dependencies, native browser APIs, Svelte 5 runes &nbsp;&nbsp;&bull;&nbsp;&nbsp;
  </span>
</Marquee>

<!-- QR Code -->
<h3 data-tour="qr">QR Code</h3>
<QRCode value="https://github.com/dryui" size={160} />

<!-- Code Block -->
<h3 data-tour="code">Code Block</h3>
<CodeBlock
  code={sampleCode}
  language="svelte"
  showLineNumbers
  showCopyButton
/>

<!-- Markdown Renderer -->
<h3 data-tour="markdown">Markdown Renderer</h3>
<div class="markdown-container">
  <MarkdownRenderer content={markdownContent} />
</div>

<!-- VirtualList -->
<h3>VirtualList (10,000 items)</h3>
<div class="virtual-container">
  <VirtualList items={virtualItems} itemHeight={48}>
    {#snippet children({ item, style })}
      <div {style} class="virtual-item">
        <strong>{item.label}</strong>
        <span class="muted">{item.description}</span>
      </div>
    {/snippet}
  </VirtualList>
</div>

<!-- InfiniteScroll -->
<h3>Infinite Scroll</h3>
<div class="infinite-container">
  <InfiniteScroll onLoadMore={loadMore} {hasMore} {loading}>
    {#snippet children()}
      {#each infiniteItems as item}
        <div class="infinite-item">{item}</div>
      {/each}
    {/snippet}
  </InfiniteScroll>
</div>

<!-- DataGrid -->
<h3>DataGrid</h3>
<DataGrid.Root items={gridData} pageSize={5}>
  <DataGrid.Table>
    <DataGrid.Header>
      <DataGrid.Column key="name" sortable>Name</DataGrid.Column>
      <DataGrid.Column key="role" sortable filterable>Role</DataGrid.Column>
      <DataGrid.Column key="department" sortable>Department</DataGrid.Column>
      <DataGrid.Column key="salary" sortable>Salary</DataGrid.Column>
    </DataGrid.Header>
    <DataGrid.Body>
      {#snippet children({ items })}
        {@const rows = items as GridRow[]}
        {#each rows as item}
          <DataGrid.Row>
            <DataGrid.Cell>{item.name}</DataGrid.Cell>
            <DataGrid.Cell>{item.role}</DataGrid.Cell>
            <DataGrid.Cell>{item.department}</DataGrid.Cell>
            <DataGrid.Cell>${item.salary.toLocaleString()}</DataGrid.Cell>
          </DataGrid.Row>
        {/each}
      {/snippet}
    </DataGrid.Body>
  </DataGrid.Table>
  <DataGrid.Pagination />
</DataGrid.Root>

<!-- DragAndDrop -->
<h3>Drag and Drop</h3>
<DragAndDrop.Root items={dndItems} onReorder={(items) => dndItems = items}>
  {#snippet children()}
    {#each dndItems as item, i}
      <DragAndDrop.Item index={i}>
        {#snippet children({ isDragging, isOver })}
          <div class="dnd-item" class:dragging={isDragging} class:over={isOver}>
            <DragAndDrop.Handle index={i} />
            <span>{item}</span>
          </div>
        {/snippet}
      </DragAndDrop.Item>
    {/each}
  {/snippet}
</DragAndDrop.Root>

<!-- Transfer -->
<h3>Transfer</h3>
<Transfer.Root {sourceItems} bind:targetItems>
  {#snippet children()}
    <div class="transfer-layout">
      <Transfer.List type="source" title="Available Languages" />
      <Transfer.Actions />
      <Transfer.List type="target" title="Selected Languages" />
    </div>
  {/snippet}
</Transfer.Root>

<!-- RichTextEditor -->
<h3>Rich Text Editor</h3>
<RichTextEditor.Root bind:value={editorValue}>
  <RichTextEditor.Toolbar />
  <RichTextEditor.Content />
</RichTextEditor.Root>

<!-- FloatButton -->
<FloatButton.Root>
  <FloatButton.Trigger>+</FloatButton.Trigger>
  <FloatButton.Action onclick={() => alert('Action 1')}>1</FloatButton.Action>
  <FloatButton.Action onclick={() => alert('Action 2')}>2</FloatButton.Action>
</FloatButton.Root>

<!-- Tour -->
<div class="row">
  <Button variant="outline" onclick={() => tourActive = true}>Start Tour</Button>
  <VisuallyHidden>Tour demonstrates step-by-step guidance</VisuallyHidden>
</div>
<Tour.Root steps={tourSteps} bind:active={tourActive}>
  {#snippet children()}
    <Tour.Tooltip />
  {/snippet}
</Tour.Root>

<style>
  h2 { margin: 2rem 0 1rem; }
  h3 { margin: 1.5rem 0 0.75rem; }

  .row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
  }

  .marquee-content {
    font-size: var(--dry-text-sm-size);
    color: var(--dry-color-muted);
  }

  .markdown-container {
    max-width: 600px;
    padding: 1rem;
    border: 1px solid var(--dry-color-border);
    border-radius: var(--dry-radius-md);
  }

  .virtual-container {
    height: 300px;
    border: 1px solid var(--dry-color-border);
    border-radius: var(--dry-radius-md);
    position: relative;
    isolation: isolate;
    overflow: hidden;
  }

  .virtual-item {
    display: flex;
    flex-direction: column;
    padding: 0.5rem 1rem;
    border-bottom: 1px solid var(--dry-color-border);
  }

  .muted {
    font-size: var(--dry-text-xs-size);
    color: var(--dry-color-muted);
  }

  .infinite-container {
    height: 250px;
    overflow-y: auto;
    border: 1px solid var(--dry-color-border);
    border-radius: var(--dry-radius-md);
  }

  .infinite-item {
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--dry-color-border);
  }

  .dnd-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    margin-bottom: 0.25rem;
    background: var(--dry-color-surface);
    border: 1px solid var(--dry-color-border);
    border-radius: var(--dry-radius-md);
  }

  .dnd-item.dragging {
    opacity: 0.5;
  }

  .dnd-item.over {
    border-color: var(--dry-color-primary);
  }

  .transfer-layout {
    display: flex;
    gap: 0.5rem;
    align-items: flex-start;
  }
</style>