Button
UI::ButtonButtons in every variant and size, with icons and a loading state.
# VelvetKit
## Button (`UI::Button`)
Buttons in every variant and size, with icons and a loading state.
Add it: `bin/rails generate velvetkit:add button`
Also adds: `icon`, `spinner`
### Arguments
```ruby
UI::Button.new(variant: :default, size: :default, type: :button, full_width: false, loading: false, disabled: false, as_child: false, class: nil, **html_attrs)
```
Slots: `with_leading_icon`, `with_trailing_icon`
### Example: All Variants
```erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(variant: :default)) { "Default" } %>
<%= render(UI::Button.new(variant: :destructive)) { "Destructive" } %>
<%= render(UI::Button.new(variant: :outline)) { "Outline" } %>
<%= render(UI::Button.new(variant: :secondary)) { "Secondary" } %>
<%= render(UI::Button.new(variant: :ghost)) { "Ghost" } %>
<%= render(UI::Button.new(variant: :link)) { "Link" } %>
</div>
```
### Example: All Sizes
```erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(size: :sm)) { "Small" } %>
<%= render(UI::Button.new(size: :default)) { "Default" } %>
<%= render(UI::Button.new(size: :lg)) { "Large" } %>
<%= render(UI::Button.new(size: :icon)) { "I" } %>
</div>
```
### Example: With Leading Icon
```erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(variant: :default)) do |btn|
btn.with_leading_icon(name: "lock")
"Sign in"
end %>
<%= render(UI::Button.new(variant: :outline)) do |btn|
btn.with_leading_icon(name: "eye")
"Preview"
end %>
<%= render(UI::Button.new(variant: :secondary)) do |btn|
btn.with_leading_icon(name: "badge-check")
"Verified"
end %>
<%= render(UI::Button.new(variant: :destructive)) do |btn|
btn.with_leading_icon(name: "x")
"Delete"
end %>
<%= render(UI::Button.new(variant: :ghost)) do |btn|
btn.with_leading_icon(name: "user")
"Profile"
end %>
</div>
```
### Example: With Trailing Icon
```erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(variant: :default)) do |btn|
btn.with_trailing_icon(name: "arrow-right")
"Continue"
end %>
<%= render(UI::Button.new(variant: :outline)) do |btn|
btn.with_trailing_icon(name: "chevrons-up-down")
"Sort"
end %>
<%= render(UI::Button.new(variant: :link)) do |btn|
btn.with_trailing_icon(name: "arrow-right")
"Read more"
end %>
</div>
```
### Example: With Both Icons
```erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(variant: :default)) do |btn|
btn.with_leading_icon(name: "lock")
btn.with_trailing_icon(name: "arrow-right")
"Sign in"
end %>
<%= render(UI::Button.new(variant: :outline, size: :lg)) do |btn|
btn.with_leading_icon(name: "eye")
btn.with_trailing_icon(name: "chevrons-up-down")
"Preview options"
end %>
</div>
```
### Example: Icon Only
```erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(variant: :default, size: :icon)) do |btn|
btn.with_leading_icon(name: "menu")
end %>
<%= render(UI::Button.new(variant: :outline, size: :icon)) do |btn|
btn.with_leading_icon(name: "user")
end %>
<%= render(UI::Button.new(variant: :secondary, size: :icon)) do |btn|
btn.with_leading_icon(name: "panel-left")
end %>
<%= render(UI::Button.new(variant: :ghost, size: :icon)) do |btn|
btn.with_leading_icon(name: "x")
end %>
<%= render(UI::Button.new(variant: :destructive, size: :icon)) do |btn|
btn.with_leading_icon(name: "x")
end %>
</div>
```
### Example: Loading
```erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(loading: true)) { "Saving" } %>
<%= render(UI::Button.new(variant: :outline, loading: true)) do |btn|
btn.with_leading_icon(name: "lock")
"Authenticating"
end %>
<%= render(UI::Button.new(variant: :destructive, loading: true)) do |btn|
btn.with_trailing_icon(name: "arrow-right")
"Deleting"
end %>
<%= render(UI::Button.new(variant: :default, size: :icon, loading: true)) %>
</div>
```
### Example: Disabled
```erb
<div class="flex flex-col gap-6">
<div>
<p class="text-xs text-muted-foreground mb-2">All variants — disabled</p>
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(variant: :default, disabled: true)) { "Default" } %>
<%= render(UI::Button.new(variant: :destructive, disabled: true)) { "Destructive" } %>
<%= render(UI::Button.new(variant: :outline, disabled: true)) { "Outline" } %>
<%= render(UI::Button.new(variant: :secondary, disabled: true)) { "Secondary" } %>
<%= render(UI::Button.new(variant: :ghost, disabled: true)) { "Ghost" } %>
<%= render(UI::Button.new(variant: :link, disabled: true)) { "Link" } %>
</div>
</div>
<div>
<p class="text-xs text-muted-foreground mb-2">Disabled with icons</p>
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(disabled: true)) do |btn|
btn.with_leading_icon(name: "lock")
"Locked"
end %>
<%= render(UI::Button.new(variant: :outline, disabled: true)) do |btn|
btn.with_trailing_icon(name: "arrow-right")
"Continue"
end %>
<%= render(UI::Button.new(variant: :destructive, size: :icon, disabled: true)) do |btn|
btn.with_leading_icon(name: "x")
end %>
</div>
</div>
<div>
<p class="text-xs text-muted-foreground mb-2">Loading auto-disables (cursor-not-allowed shows on hover)</p>
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(loading: true)) { "Saving" } %>
<%= render(UI::Button.new(variant: :destructive, loading: true)) { "Deleting" } %>
<%= render(UI::Button.new(variant: :outline, loading: true)) { "Loading" } %>
</div>
</div>
</div>
```
bin/rails generate velvetkit:add button
- Stimulus
- None, it's server-rendered
- Installs
- Nothing
Examples
All Variants
variants.html.erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(variant: :default)) { "Default" } %>
<%= render(UI::Button.new(variant: :destructive)) { "Destructive" } %>
<%= render(UI::Button.new(variant: :outline)) { "Outline" } %>
<%= render(UI::Button.new(variant: :secondary)) { "Secondary" } %>
<%= render(UI::Button.new(variant: :ghost)) { "Ghost" } %>
<%= render(UI::Button.new(variant: :link)) { "Link" } %>
</div>
All Sizes
sizes.html.erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(size: :sm)) { "Small" } %>
<%= render(UI::Button.new(size: :default)) { "Default" } %>
<%= render(UI::Button.new(size: :lg)) { "Large" } %>
<%= render(UI::Button.new(size: :icon)) { "I" } %>
</div>
With Leading Icon
with_leading_icon.html.erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(variant: :default)) do |btn|
btn.with_leading_icon(name: "lock")
"Sign in"
end %>
<%= render(UI::Button.new(variant: :outline)) do |btn|
btn.with_leading_icon(name: "eye")
"Preview"
end %>
<%= render(UI::Button.new(variant: :secondary)) do |btn|
btn.with_leading_icon(name: "badge-check")
"Verified"
end %>
<%= render(UI::Button.new(variant: :destructive)) do |btn|
btn.with_leading_icon(name: "x")
"Delete"
end %>
<%= render(UI::Button.new(variant: :ghost)) do |btn|
btn.with_leading_icon(name: "user")
"Profile"
end %>
</div>
With Trailing Icon
with_trailing_icon.html.erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(variant: :default)) do |btn|
btn.with_trailing_icon(name: "arrow-right")
"Continue"
end %>
<%= render(UI::Button.new(variant: :outline)) do |btn|
btn.with_trailing_icon(name: "chevrons-up-down")
"Sort"
end %>
<%= render(UI::Button.new(variant: :link)) do |btn|
btn.with_trailing_icon(name: "arrow-right")
"Read more"
end %>
</div>
With Both Icons
with_both_icons.html.erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(variant: :default)) do |btn|
btn.with_leading_icon(name: "lock")
btn.with_trailing_icon(name: "arrow-right")
"Sign in"
end %>
<%= render(UI::Button.new(variant: :outline, size: :lg)) do |btn|
btn.with_leading_icon(name: "eye")
btn.with_trailing_icon(name: "chevrons-up-down")
"Preview options"
end %>
</div>
Icon Only
icon_only.html.erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(variant: :default, size: :icon)) do |btn|
btn.with_leading_icon(name: "menu")
end %>
<%= render(UI::Button.new(variant: :outline, size: :icon)) do |btn|
btn.with_leading_icon(name: "user")
end %>
<%= render(UI::Button.new(variant: :secondary, size: :icon)) do |btn|
btn.with_leading_icon(name: "panel-left")
end %>
<%= render(UI::Button.new(variant: :ghost, size: :icon)) do |btn|
btn.with_leading_icon(name: "x")
end %>
<%= render(UI::Button.new(variant: :destructive, size: :icon)) do |btn|
btn.with_leading_icon(name: "x")
end %>
</div>
Loading
loading.html.erb
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(loading: true)) { "Saving" } %>
<%= render(UI::Button.new(variant: :outline, loading: true)) do |btn|
btn.with_leading_icon(name: "lock")
"Authenticating"
end %>
<%= render(UI::Button.new(variant: :destructive, loading: true)) do |btn|
btn.with_trailing_icon(name: "arrow-right")
"Deleting"
end %>
<%= render(UI::Button.new(variant: :default, size: :icon, loading: true)) %>
</div>
Disabled
All variants — disabled
Disabled with icons
Loading auto-disables (cursor-not-allowed shows on hover)
disabled.html.erb
<div class="flex flex-col gap-6">
<div>
<p class="text-xs text-muted-foreground mb-2">All variants — disabled</p>
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(variant: :default, disabled: true)) { "Default" } %>
<%= render(UI::Button.new(variant: :destructive, disabled: true)) { "Destructive" } %>
<%= render(UI::Button.new(variant: :outline, disabled: true)) { "Outline" } %>
<%= render(UI::Button.new(variant: :secondary, disabled: true)) { "Secondary" } %>
<%= render(UI::Button.new(variant: :ghost, disabled: true)) { "Ghost" } %>
<%= render(UI::Button.new(variant: :link, disabled: true)) { "Link" } %>
</div>
</div>
<div>
<p class="text-xs text-muted-foreground mb-2">Disabled with icons</p>
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(disabled: true)) do |btn|
btn.with_leading_icon(name: "lock")
"Locked"
end %>
<%= render(UI::Button.new(variant: :outline, disabled: true)) do |btn|
btn.with_trailing_icon(name: "arrow-right")
"Continue"
end %>
<%= render(UI::Button.new(variant: :destructive, size: :icon, disabled: true)) do |btn|
btn.with_leading_icon(name: "x")
end %>
</div>
</div>
<div>
<p class="text-xs text-muted-foreground mb-2">Loading auto-disables (cursor-not-allowed shows on hover)</p>
<div class="flex flex-wrap items-center gap-3">
<%= render(UI::Button.new(loading: true)) { "Saving" } %>
<%= render(UI::Button.new(variant: :destructive, loading: true)) { "Deleting" } %>
<%= render(UI::Button.new(variant: :outline, loading: true)) { "Loading" } %>
</div>
</div>
</div>
API
Arguments
UI::Button.new(variant: :default, size: :default, type: :button, full_width: false, loading: false, disabled: false, as_child: false, class: nil, **html_attrs)
Slots:
with_leading_icon
with_trailing_icon