Theming
Every component resolves through a theme — daisyUI by default, bare semantic HTML on demand.
The theme seam#
A theme is a map from component roles (:input, :select, :control, :label, :submit, :row, …) to component classes. The stable interface is the binding contract — the leaf initializer signatures (*modifiers, name:, id:, value:, error:, required:, …) — so the same form class renders under any theme.
With the daisyui gem loaded, the daisy theme is the default: every leaf delegates its markup and variant stacking to a real daisyUI component. Without it, the Plain theme takes over automatically. daisyui is a soft dependency — a non-daisyUI project never installs a UI kit it doesn't render.
The Plain theme#
Plain renders bare semantic HTML with zero styling classes and the same binding: names, ids, values, required, the hidden unchecked-value pair for checkboxes. Variants are accepted and ignored. Instead of styling, it emits stable hooks for your own CSS:
aria-invalidon invalid controls,role="alert"+data-field-erroron error messages,data-field-hinton hints,data-form-rowon rows,- a semantic
fieldset/legendfor groups,abbrfor the required marker.
Documented degradations: searchable: selects fall back to the native select (no choices.js) and rich_textarea falls back to a plain textarea.
Selecting a theme#
render UserForm.new(model: @user, theme: :plain) # per render
class AdminForm < Forms::Base
form_options theme: :plain # per class
end
PhlexForms.configure { |c| c.theme = :plain } # global defaulttheme: accepts :daisy, :plain, or a PhlexForms::Theme instance. Asking for :daisy without the daisyui gem raises a clear FeatureUnavailable.
Overriding single roles#
Theme#with swaps individual roles — your component just has to honor the role's initializer contract. Subclassing the shipped component is the easy way to stay contract-compatible:
class BrandInput < Forms::Plain::Input
def view_template
input(type: @type, value: @value.to_s, class: "brand-input", **unstyled_attributes.except(:class))
end
end
PhlexForms.configure do |c|
c.theme = PhlexForms::Theme.resolve(:plain).with(input: BrandInput)
endDaisyUI::* inside view_template, so they load fine without the gem — they just must not be rendered. Theme resolution guarantees that.