Hybrid (ViewComponent + Lit) Approach

The component is invoked in Rails via Neem::ButtonComponent, which now acts as a server-side wrapper that outputs the <neem-button> custom element. This provides the best of both worlds: Ruby-native syntax and standards-based client-side encapsulated UI.

Primary Button

Maps to Kotlin PrimaryButton. Full-width, 64px height via token.

Secondary Button

Maps to Kotlin SecondaryButton. 2px outlined border from token.

With Icons

Leading and trailing icons placed inside .neem-button__content.

Disabled State

Kotlin: enabled = falseneem-button--disabled class.

The Hybrid Output (HTML)

The ViewComponent renders the custom element. All complexity is hidden from the Rails developer.

<!-- ViewComponent output -->
<neem-button
  label="Confirm Payment"
  variant="primary"
  loading="true"
></neem-button>

Usage — Ruby (ViewComponent)

In a Rails app, Neem::ButtonComponent generates the markup above.

# Neem::ButtonComponent (packages/design-system/app/components/neem/button_component.rb)
class ButtonComponent < ViewComponent::Base
  def initialize(label:, variant: :primary, disabled: false, loading: false,
                 leading_icon: nil, trailing_icon: nil)
    @label         = label
    @variant       = variant.to_sym
    @disabled      = disabled
    @loading       = loading
    @leading_icon  = leading_icon
    @trailing_icon = trailing_icon
  end

  def before_render
    @classes = [
      "neem-button",
      "neem-button--#{@variant}",
      ("neem-button--loading"  if @loading),
      ("neem-button--disabled" if @disabled)
    ].compact.join(" ")
  end
end

# In any .html.erb template:
<%= render Neem::ButtonComponent.new(
  label:        "Confirm Payment",
  variant:      :primary,
  leading_icon: icon_svg.html_safe
) %>

Source — button.css (key rules, no hardcoded values)

.neem-button {
  height:        var(--neem-spacing-extra-extra-large); /* 64px — Kotlin: .height(64.dp)    */
  border-radius: var(--neem-shapes-large);              /* 16px — Kotlin: NeemTheme.shapes.large */
  font-size:     var(--neem-typography-label-large-size); /* 16px — TextVariant.labelLargeBold */
  transition:    all var(--neem-motion-duration-short) var(--neem-motion-easing-standard);
}
.neem-button--primary   { background: var(--neem-surface-primary); color: var(--neem-text-on-primary); }
.neem-button--secondary { border: var(--neem-border-width-bold) solid var(--neem-border-secondary); }
.neem-button:disabled   { background: var(--neem-surface-disabled-container); color: var(--neem-text-disabled); }