Hybrid 2: Tailwind + ViewComponent

The button is rendered purely on the server using Neem::TailwindButtonComponent. Instead of BEM classes, it uses Tailwind utility classes driven by values from our design tokens dynamically mapped precisely to the correct sizes and colors. No Lit or Shadow DOM—just fast, server-rendered atomic CSS.

Primary — Default

Maps to Kotlin PrimaryButton. Rendered without BEM classes.

Secondary — Default

2px outlined border rendered using Tailwind's arbitrary border values.

Primary — Loading

Loading state: content invisible, animated dots shown.

Secondary — Loading

Secondary loading state. Same dot animation, outlined style preserved.

Primary — Disabled

Kotlin: enabled = falsesurface.disabledContainer.

Secondary — Disabled

Kotlin: enabled = falseborder.disabled + text.disabled.

Primary — With Leading Icon

Leading icon via an inline SVG span. Sized with token references.

Secondary — With Trailing Icon

Trailing icon via an inline SVG span.

How Tailwind consumes design tokens

Tailwind's arbitrary value syntax maps directly to Neem CSS custom properties. No hardcoded values.

<!-- Primary Button — every value is a token reference -->
<button class="
  h-[var(--neem-spacing-extra-extra-large)]
  px-[var(--neem-spacing-medium)]
  rounded-[var(--neem-shapes-large)]
  text-[length:var(--neem-typography-label-large-size)]
  font-bold
  tracking-[var(--neem-typography-label-large-letter-spacing)]
  leading-[var(--neem-typography-label-large-line-height)]
  bg-[var(--neem-surface-primary)]
  text-[var(--neem-text-on-primary)]
  transition-all
  duration-[var(--neem-motion-duration-short)]
  ease-[var(--neem-motion-easing-standard)]
">
  Confirm Payment
</button>

<!-- Disabled state uses disabled-container tokens -->
<button class="... bg-[var(--neem-surface-disabled-container)]
  text-[var(--neem-text-disabled)]
  border-[var(--neem-border-disabled)]
  cursor-not-allowed pointer-events-none" disabled>
  Not Available
</button>

Source — tailwind_button_component.rb

The Ruby ViewComponent generates Tailwind classes with token references. Compatible with ERB templates.

# app/components/neem/tailwind_button_component.rb
module Neem
  class TailwindButtonComponent < ViewComponent::Base
    BASE = %w[
      inline-flex items-center justify-center w-full
      h-[var(--neem-spacing-extra-extra-large)]
      px-[var(--neem-spacing-medium)]
      rounded-[var(--neem-shapes-large)]
      font-bold
      text-[length:var(--neem-typography-label-large-size)]
      tracking-[var(--neem-typography-label-large-letter-spacing)]
      transition-all
      duration-[var(--neem-motion-duration-short)]
    ].freeze

    PRIMARY = %w[
      bg-[var(--neem-surface-primary)]
      text-[var(--neem-text-on-primary)]
      hover:opacity-90
    ].freeze

    SECONDARY = %w[
      bg-transparent
      border border-[length:var(--neem-border-width-bold)]
      border-[var(--neem-border-secondary)]
      text-[var(--neem-text-secondary)]
      hover:bg-[var(--neem-surface-secondary-container)]
    ].freeze
  end
end

<!-- In any .html.erb template -->
<%= render Neem::TailwindButtonComponent.new(
  label: "Confirm Payment",
  variant: :primary,
  theme: :blue
) %>