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 = false → surface.disabledContainer.
Secondary — Disabled
Kotlin: enabled = false → border.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
) %>