Packages

Sutra UI - We define the rules, so you don't have to. A Phoenix LiveView component library with 55 components and CSS-first theming.

Current section

Files

Jump to
sutra_ui cheatsheets components.cheatmd
Raw

cheatsheets/components.cheatmd

# Components Cheatsheet
Quick reference for all 55 Sutra UI components.
For design rules and composition guidance, see `SKILL.md`,
`skill/patterns.md`, and `skill/recipes.md`. For form-specific patterns, use
`forms.cheatmd`.
Browse the live component examples at [sutraui.gurujada.com](https://sutraui.gurujada.com).
Icon examples use inline SVG intentionally. Sutra UI does not provide a general
icon component or bundled icon set; use your app's existing icon helper if it
has one, otherwise use inline accessible SVG.
## Foundation
{: .col-2}
### Button
```heex
<.button>Click me</.button>
<.button variant="secondary">Secondary</.button>
<.button variant="destructive">Delete</.button>
<.button variant="outline">Outline</.button>
<.button variant="ghost">Ghost</.button>
<.button variant="link">Link</.button>
```
### Button Sizes
```heex
<.button size="sm">Small</.button>
<.button size="default">Default</.button>
<.button size="lg">Large</.button>
<.button size="icon" aria-label="Settings">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
</.button>
```
### Button Navigation
```heex
<.button navigate={~p"/users"}>Navigate</.button>
<.button patch={~p"/users/1"}>Patch</.button>
<.button href="https://example.com">Link</.button>
```
### Button States
```heex
<.button loading>Loading...</.button>
<.button disabled>Disabled</.button>
```
### Badge
```heex
<.badge>Default</.badge>
<.badge variant="secondary">Secondary</.badge>
<.badge variant="destructive">Error</.badge>
<.badge variant="outline">Outline</.badge>
```
### Spinner
```heex
<.spinner />
<.spinner size="sm" />
<.spinner size="lg" />
```
### Kbd
```heex
<.kbd>⌘</.kbd>
<.kbd>Ctrl</.kbd> + <.kbd>C</.kbd>
```
## Form Controls
{: .col-2}
### Input
```heex
<.input type="text" name="name" />
<.input type="email" name="email" placeholder="you@example.com" />
<.input type="password" name="password" />
<.input type="number" name="quantity" min="0" max="100" />
```
### Input Types
| Type | Usage |
|------|-------|
| `text` | General text |
| `email` | Email addresses |
| `password` | Passwords |
| `number` | Numeric values |
| `tel` | Phone numbers |
| `url` | URLs |
| `search` | Search fields |
| `date` | Native date input |
| `time` | Native time input |
| `datetime-local` | Native date/time input |
### Textarea
```heex
<.textarea name="bio" rows="4" />
<.textarea name="notes" placeholder="Enter notes..." />
```
### Label
```heex
<.label for="email">Email</.label>
```
### Checkbox
```heex
<.checkbox id="terms" name="terms" />
<.checkbox id="newsletter" name="newsletter" checked />
```
### Switch
```heex
<.switch id="notifications" name="notifications" />
<.switch id="dark-mode" name="dark_mode" checked />
```
### Radio Group
```heex
<.radio_group name="plan">
<:radio value="free" label="Free" />
<:radio value="pro" label="Pro" />
<:radio value="enterprise" label="Enterprise" />
</.radio_group>
```
### Select
```heex
<.select id="country" name="country" value={@country}>
<:trigger>{@country_label || "Select country"}</:trigger>
<.select_option value="us" label="United States" />
<.select_option value="uk" label="United Kingdom" />
<.select_option value="ca" label="Canada" />
</.select>
```
### Slider
```heex
<.slider id="volume" name="volume" min="0" max="100" value="50" />
```
### Range Slider
```heex
<.range_slider
id="price-range"
name="price"
min="0"
max="1000"
value_min="100"
value_max="500"
/>
```
### Live Select
```heex
<.live_component
module={SutraUI.LiveSelect}
id="users"
field={@form[:user_id]}
placeholder="Search users..."
/>
```
```elixir
def handle_event("live_select_change", %{"text" => text, "id" => id}, socket) do
users = Accounts.search_users(text)
options = Enum.map(users, &{&1.name, &1.id})
send_update(SutraUI.LiveSelect, id: id, options: options)
{:noreply, socket}
end
```
### Input Group
```heex
<.input_group>
<:prefix>$</:prefix>
<input type="number" name="amount" class="input pl-7 pr-12" />
<:suffix>.00</:suffix>
</.input_group>
<.input_group>
<:prefix type="icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
</:prefix>
<input type="search" name="q" class="input pl-9" placeholder="Search..." />
</.input_group>
```
### Input OTP
```heex
<.input_otp id="mfa-code" name="code" groups={[3, 3]} />
<.input_otp id="pin" name="pin" length={4} pattern="[0-9]" />
```
### File Upload
```heex
<.file_upload upload={@uploads.avatar} label="Upload avatar" description="PNG or JPG up to 2 MB" />
```
### Form (LiveView)
```heex
<.form for={@form} class="form" phx-change="validate" phx-submit="save">
<.input field={@form[:name]} label="Name" />
<.input field={@form[:email]} type="email" label="Email" />
<.button type="submit">Save</.button>
</.form>
```
### Filter Bar
```heex
<.filter_bar on_change="filter">
<:filter>
<.input type="search" name="q" placeholder="Search..." />
</:filter>
<:filter>
<.input type="select" name="status" options={@status_options} />
</:filter>
</.filter_bar>
```
## Layout
{: .col-2}
### Card
```heex
<.card>
<:header>
<h2>Card Title</h2>
<p>Card description</p>
</:header>
<:content>
Card content goes here
</:content>
<:footer>
<.button>Action</.button>
</:footer>
</.card>
```
### Header
```heex
<.header>
Page Title
<:subtitle>Optional subtitle</:subtitle>
<:actions>
<.button>Action</.button>
</:actions>
</.header>
```
### Table
```heex
<.data_table id="users" rows={@users}>
<:col :let={user} label="Name">{user.name}</:col>
<:col :let={user} label="Email">{user.email}</:col>
<:action :let={user}>
<.button size="sm" variant="ghost">Edit</.button>
</:action>
</.data_table>
```
### Item
```heex
<.item>
<:leading>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>
</:leading>
<:title>Document Title</:title>
<:description>Description text</:description>
<:trailing><.button size="sm">View</.button></:trailing>
</.item>
```
### Drawer
```heex
<%# Basic drawer with toggle button %>
<.drawer_trigger for="main-drawer" />
<.drawer id="main-drawer">
<:header>
<img src="/logo.svg" alt="Logo" />
</:header>
<.drawer_group label="Navigation">
<.drawer_item href="/">Home</.drawer_item>
<.drawer_item href="/users">Users</.drawer_item>
</.drawer_group>
<:footer>
<.drawer_item href="/settings">Settings</.drawer_item>
</:footer>
</.drawer>
<%# Initially open on desktop %>
<.drawer_trigger for="app-drawer" />
<.drawer id="app-drawer" open>
<.drawer_group label="Main">
<.drawer_item href="/">Home</.drawer_item>
<.drawer_item href="/dashboard">Dashboard</.drawer_item>
</.drawer_group>
</.drawer>
<%# Right-side drawer with custom trigger %>
<.drawer_trigger for="filter-drawer" variant="outline">
Filters
</.drawer_trigger>
<.drawer id="filter-drawer" side="right">
<.drawer_group label="Filters">
<.drawer_item href="/active">Active</.drawer_item>
</.drawer_group>
</.drawer>
```
### Stepper
```heex
<.stepper current={2}>
<:step>
<h4>Profile</h4>
<p>Add your details</p>
</:step>
<:step>
<h4>Workspace</h4>
<p>Configure defaults</p>
</:step>
<:step>
<h4>Invite</h4>
<p>Bring in teammates</p>
</:step>
</.stepper>
```
### Stepper Wizard
```heex
<.form for={@form} phx-change="validate" phx-submit="submit_wizard">
<.stepper_wizard id="checkout" current={@step} errors={@step_errors}>
<:step id="shipping" label="Shipping">
<.shipping_fields form={@form} />
</:step>
<:step id="payment" label="Payment">
<.payment_fields form={@form} />
</:step>
<:step id="confirm" label="Confirm">
<.order_review order={@order} />
</:step>
<:actions>
<.button type="button" phx-click="previous">Back</.button>
<.button type="submit">Continue</.button>
</:actions>
</.stepper_wizard>
</.form>
```
### Tree View
```heex
<.tree_view id="project-files" label="Project files">
<.tree_item label="lib" expanded>
<.tree_item label="app.ex" />
</.tree_item>
<.tree_item label="README.md" href="/readme" />
</.tree_view>
```
## Feedback
{: .col-2}
### Alert
```heex
<.alert>
<:title>Default alert message</:title>
</.alert>
<.alert variant="destructive">
<:title>Error message</:title>
</.alert>
<.alert>
<:title>Heads up!</:title>
<:description>You can add components to your app using the CLI.</:description>
</.alert>
```
### Flash
```heex
<.flash kind={:info} flash={@flash} />
<.flash kind={:error} flash={@flash} title="Something went wrong" />
```
### Progress
```heex
<.progress value={33} />
<.progress value={66} />
```
### Skeleton
```heex
<.skeleton class="h-4 w-[250px]" />
<.skeleton class="h-12 w-12 rounded-full" />
```
### Empty
```heex
<.empty>
<:icon>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-12 w-12" aria-hidden="true"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg>
</:icon>
<:title>No messages</:title>
<:description>Get started by sending your first message.</:description>
<:actions><.button>Send Message</.button></:actions>
</.empty>
```
### Loading State
```heex
<.loading_state message="Loading data..." />
<.loading_state message="Please wait..." wrap_card={false} />
```
### Toast
```heex
<.toast_container flash={@flash} />
<.toast id="saved-toast" variant="default">
<:title>Changes saved</:title>
</.toast>
<.toast id="error-toast" variant="destructive">
<:title>Something went wrong</:title>
</.toast>
```
## Overlay
{: .col-2}
### Dialog
```heex
<.button phx-click="open_confirm">Open Dialog</.button>
<.dialog id="confirm-dialog" show={@show_confirm} on_cancel="close_confirm">
<:title>Confirm Action</:title>
<:description>Are you sure?</:description>
Dialog content here
<:footer>
<.button variant="outline" phx-click="close_confirm">
Cancel
</.button>
<.button phx-click="confirm">Confirm</.button>
</:footer>
</.dialog>
```
### Dialog JS Commands
```heex
<.button phx-click={SutraUI.Dialog.show_dialog("my-dialog")}>Open</.button>
<.button phx-click={SutraUI.Dialog.hide_dialog("my-dialog")}>Close</.button>
```
### Popover
```heex
<.popover id="info-popover">
<:trigger>
Info
</:trigger>
<p>Popover content here</p>
</.popover>
```
### Tooltip
```heex
<.tooltip id="help-tip" tooltip="Helpful tip">
<.button>Hover me</.button>
</.tooltip>
```
### Dropdown Menu
```heex
<.dropdown_menu id="actions-menu">
<:trigger>
Actions
</:trigger>
<.dropdown_item phx-click="edit">Edit</.dropdown_item>
<.dropdown_item phx-click="duplicate">Duplicate</.dropdown_item>
<.dropdown_separator />
<.dropdown_item phx-click="delete" variant="destructive">Delete</.dropdown_item>
</.dropdown_menu>
```
### Context Menu
```heex
<.context_menu id="message-menu">
<:trigger>
<div class="rounded-lg border p-6">Right click here</div>
</:trigger>
<.context_menu_item>Reply</.context_menu_item>
<.context_menu_item shortcut="⌘K">Copy link</.context_menu_item>
<.context_menu_separator />
<.context_menu_item variant="destructive">Delete</.context_menu_item>
</.context_menu>
```
### Command
```heex
<.command id="command-palette" placeholder="Type a command...">
<.command_group heading="Suggestions">
<.command_item id="calendar">Calendar</.command_item>
<.command_item id="search">Search</.command_item>
<.command_item id="settings">Settings</.command_item>
</.command_group>
</.command>
```
### Hover Card
```heex
<.hover_card id="user-preview">
<:trigger><button>@sutra_ui</button></:trigger>
<div class="space-y-1">
<p class="font-medium">Sutra UI</p>
<p class="text-sm text-muted-foreground">Phoenix LiveView components.</p>
</div>
</.hover_card>
```
## Navigation
{: .col-2}
### Tabs
```heex
<.tabs id="settings-tabs" default_value="account">
<:tab value="account">Account</:tab>
<:tab value="password">Password</:tab>
<:tab value="notifications">Notifications</:tab>
<:panel value="account">Account settings...</:panel>
<:panel value="password">Password settings...</:panel>
<:panel value="notifications">Notification preferences...</:panel>
</.tabs>
```
### Accordion
```heex
<.accordion id="faq">
<:item value="q1" title="What is Sutra UI?">
A Phoenix LiveView component library.
</:item>
<:item value="q2" title="Is it accessible?">
Components use semantic markup and ARIA patterns where appropriate.
</:item>
</.accordion>
```
### Breadcrumb
```heex
<.breadcrumb>
<:item href="/">Home</:item>
<:item href="/products">Products</:item>
<:item>Current Page</:item>
</.breadcrumb>
```
### Pagination
```heex
<.pagination
page={@page}
total_pages={@total_pages}
on_page_change="page_changed"
/>
```
### Tab Nav
Responsive server-side routed tab navigation. It collapses to a dropdown on small screens by default.
```heex
<.tab_nav id="settings-tabs-nav">
<:tab patch={~p"/settings/profile"} active={@tab == :profile}>
Profile
</:tab>
<:tab patch={~p"/settings/account"} active={@tab == :account}>
Account
</:tab>
</.tab_nav>
```
Use `collapse="never"` when the tab row should stay visible on small screens.
## Display
{: .col-2}
### Avatar
```heex
<.avatar src="/avatar.jpg" alt="User" />
<.avatar src={nil} initials="JD" />
<.avatar size="sm" src="/avatar.jpg" />
<.avatar size="lg" src="/avatar.jpg" />
```
### Carousel
```heex
<.carousel id="images">
<:item>
<img src="/image1.jpg" alt="Image 1" />
</:item>
<:item>
<img src="/image2.jpg" alt="Image 2" />
</:item>
<:item>
<img src="/image3.jpg" alt="Image 3" />
</:item>
</.carousel>
```
### Calendar
```heex
<.calendar year={2026} month={6} selected="2026-06-17" select_event="select_date" />
```
### Timeline
```heex
<.timeline>
<:item time="09:00">
<h3>Created</h3>
<p>Workspace created</p>
</:item>
<:item time="10:30" icon="✓">
<h3>Published</h3>
<p>Live now</p>
</:item>
</.timeline>
<.timeline>
<:item time="Today" icon="✓">
<.link navigate="/releases/1">v0.4.0 shipped</.link>
<p>Release notes and migration details.</p>
</:item>
</.timeline>
```
### Marquee
```heex
<.marquee>
<:item>Free shipping on all orders</:item>
<:item>New components released weekly</:item>
<:item>24/7 support available</:item>
</.marquee>
<.marquee speed="slow" gap="lg" direction="right">
<:item><img src="/logo-1.svg" alt="Company 1" /></:item>
<:item><img src="/logo-2.svg" alt="Company 2" /></:item>
</.marquee>
```
### Separator
```heex
<.separator />
<.separator decorative={false} />
<.separator orientation="vertical" />
```
### Theme Switcher
```heex
<.theme_switcher id="theme-toggle" />
```
## AI
{: .col-2}
### Response
```heex
<.response value={@answer} />
<.response id="answer" value={@answer} streaming reveal="word" />
<.response value={@streamed_markdown} format="markdown" streaming />
```
### Activity
```heex
<.activity>
<:item status="complete">Searched documentation</:item>
<:item status="running">
Checking relevant examples.
</:item>
<:item status="pending">Preparing summary</:item>
</.activity>
```