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 = false → neem-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); }