Esc

Components

All components Every component, with search. Accordion Sections that expand and collapse, one at a time or several. Alert A highlighted message, optionally dismissible. Avatar A person's picture, with initials when there is none. Avatar group Overlapping avatars with a "+N" count for the rest. Badge A small status label. Banner A dismissible strip across the top of a page. Breadcrumb The path to the current page, collapsing long trails. Button Buttons in every variant and size, with icons and a loading state. Button to A button that submits a one-off form, like Rails' button_to. Card A bordered container with header, content and footer. Checkbox A checkbox with label, standalone or bound to a form builder. Collapsible A section that opens and closes under its trigger, like a branch of a nav tree. Data grid A sortable, filterable, server-paged table on AG Grid. Date picker A typed date field with a calendar, on Cally. Dialog A modal dialog on the native <dialog> element. Divider A horizontal or vertical rule, optionally labelled. Form field A label, hint and error message wired to any form control. Icon A Lucide icon. Image An image with consistent sizing and corner radius. Input A text field with label, hint and error message, standalone or with a form builder. Input group A text field with addons before or after it. Label A form label with a required marker. Link A link styled as text or as a button. Menu A dropdown menu with keyboard navigation. Pagination Page links for a long list, from a Pagy object. Password input A password field with a show/hide toggle. Popover Floating content anchored to a trigger. Radio group A set of radio buttons. Native select The browser's own select, styled closed; best for phones' system pickers. Select A styled dropdown with keyboard navigation and type-ahead, posting like a native select. Sheet A panel that slides in from an edge of the screen. Sidebar A collapsible application sidebar with groups and menus. Spinner A loading indicator. Switch An on/off toggle. Table A styled table with header, rows and caption. Table of contents An "On this page" list that follows the reader's scroll. Tabs Tabbed panels with keyboard navigation. Text Body text in set sizes, weights and tones. Textarea A multi-line text field with label, hint and error, that can grow with its text. Theme toggle A light/dark/system theme switcher. Tooltip A hint shown on hover and focus. Typography Headings, lead, muted, code and blockquote text styles.

Getting started

Add VelvetKit to a Rails app and put your first component on a page. It takes about five minutes.

Working with an agent?

Give it one prompt and it does every step below, then learns the kit from llms.txt.

Build with AI
  1. 1

    Add the gem

    VelvetKit only runs generators. Nothing loads from it in production, so it goes in the development group.

    Gemfile
    gem "velvetkit", group: :development
    bundle install
  2. 2

    Install

    Choose the module your components will live under. UI is the default. You can't easily change it later, so read about namespacing if you're unsure.

    bin/rails generate velvetkit:install --namespace=UI

    What install does

    • Writes config/velvetkit.yml with your namespace
    • Adds view_component and tailwind_merge to your Gemfile
    • Copies app/assets/tailwind/velvetkit.css the design tokens, and imports it
    • Copies config/locales/velvetkit.en.yml every string the components show
    • Copies app/javascript/velvetkit_icons.ts the Lucide icons, and imports it
    • Copies the base component that every other component builds on
  3. 3

    Add components

    Name the components you want. Each one brings the components it renders, so a dialog also brings the button and icon it uses.

    bin/rails generate velvetkit:add dialog date_picker

    Run it with no names to list all 42 components, or add --all to copy every one.

  4. 4

    Render it

    app/views/settings/show.html.erb
    <%= render(UI::Dialog.new) do |dialog| %>
      <% dialog.with_trigger(variant: :outline) { "Delete account" } %>
      <% dialog.with_content do |content| %>
        <% content.with_title { "Delete your account?" } %>
        <% content.with_description { "This cannot be undone." } %>
      <% end %>
    <% end %>

    Delete your account?

    This cannot be undone.

    ← that's the real thing. Try it.

Generator options

Option What it does
--namespace=Kit Use this module instead of the one in config/velvetkit.yml.
--all Add every component.
--skip-tests Don't copy the components' Minitest tests.
--skip-previews Don't copy the Lookbook previews.
--skip-packages Print the npm packages to add instead of installing them.
--force / --skip Overwrite, or keep, files that already exist.

Questions

Yes, that's the point. It's a file in your app. Change the classes, the arguments or the markup. Its tests are copied too, so they'll tell you what you changed.

Update the gem and run add again with --force. The files are overwritten, so commit first and use git diff to review the changes and keep your own edits.

The Ruby side does. The Stimulus controllers are TypeScript, so they need a bundler such as Bun or esbuild. Importmap can't compile them.

So you never wait on us. You can fix a bug today, style a component to match your brand, or delete what you don't use, all without forking a library.