π― v0.20.0 β Power Patterns
What's New
CommandPaletteComponent
A keyboard-activated search-and-action overlay, bound to βK by default. Built on DialogComponent for the overlay shell, with grouped results, full arrow/enter/escape keyboard navigation, and three operating modes: static items, async JSON via src:, and server-rendered HTML via Turbo Frames.
Each result row is a CommandItemComponent that supports label:, url:, action:, icon:, shortcut:, and description:. Keyboard shortcuts are displayed with the new KbdComponent.
<%= midwest_command_palette(placeholder: "Type a commandβ¦") do |p| %> <% p.with_item(label: "Dashboard", url: root_path, icon: :home, group: "Pages") %> <% p.with_item(label: "Settings", url: settings_path, icon: :settings, group: "Pages") %> <% p.with_item(label: "Log out", url: logout_path, method: :post, group: "Actions") %><% end %>Midwest::TurboCommands concern
Include Midwest::TurboCommands in a controller and implement midwest_command_palette_items(query) to return item hashes filtered server-side. The concern mounts a turbo-frame endpoint that the palette fetches as the user types.
<% class DashboardController < ApplicationController %> <% include Midwest::TurboCommands %> <% turbo_commands %><% end %>Midwest::CommandPaletteBuilder
A builder DSL for assembling command items with permission gating. Pass an ActiveModel class or instance to auto-infer label:, url:, and icon: via the new RecordInference module. Explicit keyword arguments override any inferred values.
<% items = Midwest::CommandPaletteBuilder.new do |p| %> <% p.item User, permit: -> { policy.can?(:read, User) } %> <% p.item "Log out", url: logout_path, method: :post %><% end.items %>TreeViewComponent
A hierarchical expandable list for file trees, category navigation, and nested data. Each TreeNodeComponent supports expand/collapse, multi-level nesting, and optional checkbox selection with parentβchild cascade (indeterminate state included). Integrates with midwest_form_with via form.tree_view(:attribute).
<%= midwest_tree_view(selectable: true, field_name: "categories[]") do |tv| %> <% tv.with_node("Documents", expanded: true) do |n| %> <% n.with_node("Reports", value: "reports") %> <% n.with_node("Invoices", value: "invoices") %> <% end %> <% tv.with_node("Images", value: "images") %><% end %>ShowIfComponent
Declarative conditional field visibility. Wraps a form section and shows or hides it when a watched field matches a target value. Accepts watch: (CSS selector), is: (value to match), and negate: to invert the condition. Driven by a bundled Stimulus controller β no custom JS needed.
<%= midwest_show_if(watch: "#country", is: "Other") do %> <%= midwest_input(name: "country_other", label: "Please specify") %><% end %>What Changed
Form::InputComponent β floating label
Pass floating: true to render the label as placeholder text that rises above the input on focus or when a value is present. Replaces the standard above-input label with an animated floating version.
<%= midwest_input(name: "email", label: "Email address", floating: true) %>Form::SelectComponent β grouped options
Pass choices as a Hash of { "Group Label" => [[label, value], β¦] } to render <optgroup> sections. The component auto-detects grouped structure, or set grouped: true explicitly.
<%= midwest_select(name: "city", label: "City", choices: { "Midwest" => [["Chicago", "chi"], ["Detroit", "det"]], "Coasts" => [["NYC", "nyc"], ["LA", "la"]]}) %>AccordionComponent β nested accordions
An accordion can now be placed inside an accordion item. Nested accordions reset their border and background so they read as a sub-level of the parent. No API changes β just nest the components.
CardComponent β flipped: state prop
Pass flipped: true to render the card showing its back face on initial load. The user can flip to the front via the flip action. Pairs with the existing flippable: behavior and front / back content slots.
<%= midwest_card(flipped: true) do |c| %> <% c.with_front { "Front face" } %> <% c.with_back { "Back face" } %><% end %>Form::CheckboxComponent / Form::RadioComponent β tree view integration
Both components gain internal support for the TreeViewComponent selection cascade. When rendered inside a tree node, parent checkboxes automatically reflect child state (checked, unchecked, indeterminate).
What Was Removed
Nothing removed.
Dev Process
Midwest::RecordInference module
A shared utility extracted during the command palette work. Given an ActiveModel class or instance, it infers a display label, URL, and icon. Used by both CommandPaletteBuilder and BreadcrumbsComponent (which was refactored to use it, replacing its own inline inference logic).
Midwest::Routing helper
A lightweight routing module that resolves conventional Rails paths for ActiveModel objects, centralising the path-resolution logic previously duplicated across components.
New Rails Features documentation
Two new doc pages ship with the demo app: Turbo Commands (wiring up server-side command palette endpoints) and Command Palette Builder (the builder DSL and permission gating).
Expanded test coverage
New and updated test files:
CommandPaletteComponentTest,CommandItemComponentTest,KbdComponentTestβ full unit suites for the palette and its supporting components.CommandPaletteBuilderTestβ builder DSL, permission gating, record inference.RecordInferenceTestβ label, URL, and icon inference from ActiveModel objects.TreeViewComponentTestβ node rendering, expand/collapse, selectable mode, form builder integration.ShowIfComponentTestβ watch/is matching, negate mode, checkbox and radio triggers.AccordionComponentTestβ nested accordion rendering.CardComponentTestβ flipped state prop.Form::InputComponentTestβ floating label.Form::SelectComponentTestβ grouped options and auto-detection.
System tests
Browser-level system tests added for CommandPaletteComponent (hotkey activation, keyboard navigation, item selection, Turbo Frame filtering) and TreeViewComponent (expand/collapse interaction, checkbox cascade).
Code coverage
Coverage badge updated to 96%.