PhlexKit
A ruby_ui-style component kit for Phlex, styled with vanilla CSS + design tokens instead of Tailwind, and built so phlex-reactive is an optional, per-component integration — never a required dependency.
- No build step. Components ship as plain Ruby classes with co-located vanilla CSS. No Tailwind, no Node, no PostCSS. Assets are precompiled-static in the gem.
- Theme with CSS custom properties. Every component reads
--pk-*tokens viavar()+color-mix(). Redefine:rootand the whole kit re-themes live — including dark/light/system — with no rebuild. - Reactive when you want it. Components pass
**attrsstraight through Phlex'smix, so a phlex-reactive**on(:event)bundle composes onto the root element with zero coupling. Non-reactive components never touch phlex-reactive.
Install
# Gemfile
gem "phlex_kit"
bundle install
bin/rails g phlex_kit:install
The installer adds @import url("phlex_kit/phlex_kit.css"); to your
application.css, drops config/initializers/phlex_kit.rb, and prints the
Stimulus wiring. That's it — no Tailwind config, no content globs.
Usage
render PhlexKit::Button.new(variant: :primary, size: :lg) { "Save changes" }
render PhlexKit::Card.new do
render PhlexKit::CardHeader.new do
render PhlexKit::CardTitle.new { "Team" }
end
render PhlexKit::CardContent.new { "…" }
end
render PhlexKit::Badge.new(variant: :success) { "Live" }
Prefer revue-style UI::Button? Turn on the alias in the initializer:
PhlexKit.configure { |c| c.define_ui_alias = true } # UI == PhlexKit
Theming
The gem ships a default dark/light/system token set (_tokens.css). Override any
token in your own stylesheet — your app's CSS sorts ahead of the gem's, so you win:
@import url("phlex_kit/phlex_kit.css");
:root {
--pk-brand: #3b5bdb;
--pk-radius: 6px;
}
Want a completely custom palette? Delete the _tokens import line from your
manifest and define the --pk-* properties yourself. Every component has a
literal fallback in var(--pk-*, …), so nothing breaks if a token is missing.
Interactive components (Stimulus)
Dialog, Dropdown, Select, and Avatar ship plain Stimulus controllers (no phlex-reactive needed). Register them once in your Stimulus entrypoint:
import { registerPhlexKitControllers } from "phlex_kit/controllers"
registerPhlexKitControllers(application)
phlex-reactive (optional)
For components that own server-state behavior (live counters, moderation queues, cross-tab updates), add phlex-reactive and include its mixin in a component:
# Gemfile
gem "phlex-reactive"
class MyCounter < PhlexKit::BaseComponent
include Phlex::Reactive::Component
action(:increment) { @count += 1 }
# view_template uses **on(:increment) on a button — it just flows through mix
end
PhlexKit does not depend on phlex-reactive. PhlexKit.reactive? auto-detects
it; set config.reactive to force it on/off.
Ejecting components (shadcn-style)
Want to own and edit a component's source? Eject it into your app:
bin/rails g phlex_kit:component button
This copies button.rb + button.css into app/components/phlex_kit/button/
and wires its @import. Your copy shadows the gem's.
How the asset wiring works
Three engine initializers reproduce the pattern proven in production (revue):
app/componentsand the stylesheet dir go on Propshaft's load path so CSS can sit beside each.rb.- Component folders are Zeitwerk-
collapsed, sobutton/button.rbisPhlexKit::Button(notPhlexKit::Button::Button) andcard/card_header.rbisPhlexKit::CardHeader. - A private-method guard keeps Propshaft from serving Ruby source out of
public/assets/(covered bytest/assets/asset_load_path_test.rb).
Only the @import url("…") form is fingerprinted by Propshaft — a bare
@import "…" ships un-digested and 404s. The manifest always uses url().
Components
See ROADMAP.md for the full inventory and porting status.
License
MIT.