Shadwire ports the shadcn/ui Open Code model to Ruby on Rails.
The source of truth is registry/. The Rails app in sandbox/ consumes copied registry files so components are validated in a real Rails, ViewComponent, Tailwind CSS v4, and Hotwire environment.
58 components, listed with shadwire list. Search by what you need
(shadwire search form), then read a component API with shadwire info button.
| Component | Description |
|---|---|
button |
Displays a button or a link styled as a button. |
badge |
A small inline label for status, counts, or categories. |
card |
A container for grouping related content with header, body, and footer slots. |
alert |
A callout for important inline messages. |
separator |
A thin rule that visually divides content. |
avatar |
A user or entity image with a text fallback. |
accordion |
Vertically stacked sections that expand and collapse. |
scroll-area |
A scrollable region with styled scrollbars. |
icon |
A Lucide icon with shadcn-style size variants. |
input |
A single-line text field. |
label |
An accessible caption bound to a form control. |
textarea |
A multi-line text field. |
checkbox |
A binary control for opting in or selecting many items. |
radio-group |
A set of mutually exclusive options. |
switch |
A toggle for a setting that takes effect immediately. |
skeleton |
A placeholder block shown while content loads. |
progress |
A bar showing completion toward a known total. |
table |
A styled HTML table with header, body, and footer sections. |
breadcrumb |
A trail showing the current page's position in the hierarchy. |
pagination |
Navigation controls for moving between pages of results. |
tabs |
Switches between peer views in the same space. |
dialog |
A modal window overlaying the page. |
alert-dialog |
A modal that interrupts the user to confirm a consequential action. |
sheet |
A panel that slides in from an edge of the screen. |
tooltip |
A short hint shown on hover or focus. |
popover |
A floating panel anchored to a trigger. |
dropdown-menu |
A menu of actions or options triggered by a button. |
select |
A styled control for choosing one option from a list. |
sidebar |
A composable, collapsible application sidebar. |
sidebar-01 |
A documentation-style dashboard layout with a collapsible sidebar, version switcher, and search. |
aspect-ratio |
Constrains its content to a fixed width-to-height ratio. |
spinner |
An indeterminate loading indicator. |
kbd |
Displays a keyboard key or shortcut. |
empty |
A placeholder for when there is nothing to show. |
item |
A compact row with media, text, and trailing actions. |
input-group |
An input with attached addons, text, or buttons. |
button-group |
Related buttons joined into a single control. |
field |
Form field layout with label, description, and error message. |
native-select |
The browser's native select element, styled to match. |
collapsible |
A single region that expands and collapses. |
toggle |
A two-state button that stays pressed. |
toggle-group |
A set of toggle buttons acting as one control. |
slider |
Selects a numeric value from a range by dragging. |
hover-card |
A rich preview card shown on hover. |
input-otp |
A segmented field for one-time passcodes. |
drawer |
A panel that slides up from the bottom of the screen. |
context-menu |
A menu opened by right-clicking a region. |
menubar |
A horizontal application menu bar. |
navigation-menu |
Site navigation with optional rich dropdown panels. |
command |
A searchable, keyboard-driven list of commands. |
calendar |
A month grid for selecting a date or a date range. |
resizable |
Panels the user can resize by dragging a handle. |
carousel |
A horizontally or vertically swipeable set of slides. |
combobox |
A searchable single-select built from popover, command, and button. |
date-picker |
A date field that opens a calendar in a popover. |
sonner |
Transient toast notifications. |
chart |
Chart.js charts using the Shadwire theme tokens. |
data-table |
A table with sorting, filtering, pagination, and row selection. |
Helpers use the ui_* prefix.
<%= render Ui::ButtonComponent.new(variant: :outline, size: :sm) do %>
Save
<% end %>
<%= ui_button(variant: :outline, size: :sm) { "Save" } %>Icons are rendered with lucide-rails
(add gem "lucide-rails" to the consuming app). Compose them inside other
components:
<%= ui_button { (ui_icon("download") + " Download").html_safe } %>
<%= ui_button(size: :icon) { ui_icon("plus", label: "Add item") } %>Shadwire ships a shadwire CLI (packages/cli/) that installs component source
into a Rails app and keeps it in sync with the registry — the shadcn Open Code
flow. Installed files are yours; there is no runtime dependency on Shadwire.
Install it globally, or add it to the consuming app directly:
gem install shadwire # global — bootstrap with `shadwire init`
bundle add shadwire --group development # in the app — bootstrap with `bundle exec shadwire init`Bootstrap once. init adds shadwire to the app's development group (if it is
not there already) and writes the bin/shadwire binstub. That binstub is how you
run the CLI from then on — one entry point, pinned to the app's bundle:
shadwire init # writes shadwire.json + base files + bin/shadwire
bin/shadwire add button dialog # installs components and their registry dependencies
bin/shadwire list # every component in the registry cataloginit is the one command you run un-prefixed, because it is what creates the
binstub. Use bundle exec shadwire init when the gem is in the Gemfile rather
than installed globally.
Components install from the hosted registry
(https://shadwire.edumoraes.dev.br/r) by default; override with --registry
(an https:// URL or a local file:// path).
Agents / CI — every command runs non-interactively with --yes, emits
machine-readable output with --json, and can target another app with --cwd.
bin/shadwire status --json reports the whole install in one call, and
bin/shadwire diff --exit-code exits non-zero when an installed file has drifted
from the registry, so CI can fail on drift. See
packages/cli/README.md for the full command reference.
Coding agents — Claude Code, Codex, Cursor, OpenCode and ~20 others — can install the Shadwire skill for full context on the CLI and components:
npx skills add edumoraes/shadwireThe skill lives in skills/shadwire/. It carries workflow and
conventions, not data: component names, variants, props and helper names are
pulled at runtime from bin/shadwire status --json and bin/shadwire info --json,
so it always reflects what is actually installed. CI verifies that everything the
skill names still exists.
The skill's allowed-tools grant covers only the turn that invokes it, so it
stops the permission prompts for that turn and no longer. To silence them for
good, add an allow rule to the consuming app's .claude/settings.json:
{ "permissions": { "allow": ["Bash(bin/shadwire *)"] } }Agents that cannot install the skill can read the same catalog as plain text:
/r/llms.txt— every component with when to use it/r/llms-full.txt— every component's full API and usage
Edit source files in registry/rails/ui.
Sync registry files into the sandbox:
bin/sync_registryRun all tests:
rake testRun sandbox tests only:
cd sandbox
bin/rails test test/components test/integration/ui_accessibility_test.rbTheme tokens live in registry/rails/ui/styles/shadwire.css under :root and
.dark. Tailwind v4 reads them through @theme inline, so component classes
should use semantic utilities such as bg-primary, text-muted-foreground,
border-input, and ring-ring instead of hardcoded colors.
| Token | What it controls | Used by |
|---|---|---|
background / foreground |
Default app background and text color. | Page shell, page sections, and default text. |
card / card-foreground |
Elevated surfaces and the content inside them. | Card, dashboard panels, and settings panels. |
popover / popover-foreground |
Floating surfaces and the content inside them. | Popover, DropdownMenu, ContextMenu, and other overlays. |
primary / primary-foreground |
High-emphasis actions and brand surfaces. | Default Button, selected states, badges, and active accents. |
secondary / secondary-foreground |
Lower-emphasis filled actions and supporting surfaces. | Secondary buttons, secondary badges, and supporting UI. |
muted / muted-foreground |
Subtle surfaces and lower-emphasis content. | Descriptions, placeholders, empty states, helper text, and subdued surfaces. |
accent / accent-foreground |
Interactive hover, focus, and active surfaces. | Ghost buttons, menu highlight states, hovered rows, and selected items. |
destructive / destructive-foreground |
Destructive actions and error emphasis. | Destructive buttons, invalid states, and destructive menu items. |
border |
Default borders and separators. | Cards, menus, tables, separators, and layout dividers. |
input |
Form control borders and input surface treatment. | Input, Textarea, Select, and outline-style controls. |
ring |
Focus rings and outlines. | Buttons, inputs, checkboxes, menus, and other focusable controls. |
chart-1 ... chart-5 |
Default chart palette. | Charts and chart-driven dashboard blocks. |
sidebar / sidebar-foreground |
Base sidebar surface and default sidebar text. | Sidebar container and its default content. |
sidebar-primary / sidebar-primary-foreground |
High-emphasis actions inside the sidebar. | Active items, icon tiles, badges, and sidebar CTAs. |
sidebar-accent / sidebar-accent-foreground |
Hover and selected states inside the sidebar. | Sidebar menu hover states, open items, and interactive rows. |
sidebar-border |
Sidebar-specific borders and separators. | Sidebar headers, groups, and internal dividers. |
sidebar-ring |
Sidebar-specific focus rings. | Focused controls inside the sidebar. |
radius |
Base corner radius scale. | Cards, inputs, buttons, popovers, and derived radius-* tokens. |
| shadcn / React concept | Shadwire / Rails equivalent |
|---|---|
| Base class string | base_classes method |
cva variants |
frozen Ruby hashes |
cn(...) and className |
class_names(..., @class_name) |
| React props | initialize(...) keyword arguments |
children |
content |
asChild |
configurable tag: or conditional rendering |
| Radix/Base behavior | native HTML, Stimulus, or Hotwire |
Use Conventional Commits:
git commit -m "feat: add button component"
git commit -m "test: cover card component"
git commit -m "docs: update registry workflow"