Senren UI
Components / Button

Button

Stable

primary action

Usage example #

Copy this ERB into a Rails view after installing the component. The snippet below is the same code used by the live preview above.

app/views/.../button_example.html.erb
<div class="flex flex-wrap items-center justify-center gap-2">
  <%= render(Senren::ButtonComponent.new(variant: :primary))     { "Primary" } %>
  <%= render(Senren::ButtonComponent.new(variant: :secondary))   { "Secondary" } %>
  <%= render(Senren::ButtonComponent.new(variant: :destructive)) { "Destructive" } %>
  <%= render(Senren::ButtonComponent.new(variant: :ghost))       { "Ghost" } %>
  <%= render(Senren::ButtonComponent.new(variant: :link))        { "Link" } %>
</div>
<div class="mt-3 flex flex-wrap items-center justify-center gap-2">
  <%= render(Senren::ButtonComponent.new(variant: :primary, size: :sm)) { "Small" } %>
  <%= render(Senren::ButtonComponent.new(variant: :primary, size: :md)) { "Medium" } %>
  <%= render(Senren::ButtonComponent.new(variant: :primary, size: :lg)) { "Large" } %>
</div>

Install this component #

Copy the official component into your app

Use this when you want the Senren-maintained implementation copied into app/components/senren.

Terminal
bin/rails senren:add button

Create a custom component with the same conventions

Use this when you need an app-specific static component that follows Senren's ViewComponent structure.

Terminal
bin/rails generate senren:component button --no-client

At a glance #

Category Actions
Class name Senren::ButtonComponent
Stimulus
Variants default, primary, secondary, destructive, ghost, link
Depends on
Pairs with form, dialog, dropdown_menu, alert_dialog

Source #

app/components/senren/button_component.html.erb
<% base = "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-(--senren-radius) font-medium transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))] disabled:opacity-50 disabled:pointer-events-none disabled:cursor-not-allowed" %>
<% if as == :a %>
  <%= tag.a content, **root_attrs(base, href: href) %>
<% else %>
  <%= tag.button content, **root_attrs(base, type: type) %>
<% end %>
app/components/senren/button_component.rb
# frozen_string_literal: true

module Senren
  class ButtonComponent < BaseComponent
    VARIANTS = {
      default: 'bg-[hsl(var(--senren-secondary))] text-[hsl(var(--senren-secondary-foreground))] hover:opacity-90',
      primary: 'bg-[hsl(var(--senren-primary))] text-[hsl(var(--senren-primary-foreground))] hover:opacity-90',
      secondary: 'bg-[hsl(var(--senren-secondary))] text-[hsl(var(--senren-secondary-foreground))] hover:opacity-90',
      destructive: 'bg-[hsl(var(--senren-destructive))] text-[hsl(var(--senren-destructive-foreground))] hover:opacity-90',
      ghost: 'bg-transparent text-[hsl(var(--senren-foreground))] hover:bg-[hsl(var(--senren-accent))]',
      link: 'bg-transparent text-[hsl(var(--senren-primary))] underline-offset-4 hover:underline'
    }.freeze

    SIZES = {
      sm: 'h-8  px-3 text-sm',
      md: 'h-10 px-4 text-sm',
      lg: 'h-12 px-6 text-base'
    }.freeze

    def initialize(variant: :default, size: :md, type: 'button', as: :button, href: nil, class_name: nil, **html)
      super(variant: variant, size: size, class_name: class_name, **html)
      @type = type
      @as   = href ? :a : as
      @href = href
    end

    attr_reader :type, :as, :href
  end
end

AI agent rules #

Use for

  • +primary action
  • +secondary action
  • +form submit
  • +destructive action

Avoid

  • -using destructive variant for non-destructive actions

Accessibility #

  • Icon-only buttons must include aria-label.
  • Use button for actions and anchor for navigation.