Guide

Tag fields

field :tags, as: :tags renders a polished tag/chip input — daisyUI chrome over phlex-reactive's client-only tag primitives, submitting one comma-joined param.

A tag input, written once#

as: :tags renders a tag/chip input: type-ahead over your suggestions, Enter or click to add a chip, × to remove — all client-side (form state, no server round trips). phlex-forms owns the polished chrome (label, error/hint, daisyUI styling, model binding); the behavior rides on phlex-reactive's tag primitives.

Requires phlex-reactive ≥ 0.11.4 — the :tags control is only registered when it's loaded.

f.field :tags, as: :tags, suggestions: %w[Ruby Rails Hotwire Postgres]

Try it#

The widget below is the real Forms::TagField, rendered live on this page. Type to filter the suggestions, Enter or click to add a tag, × to remove one. The hidden field (inspect it) carries the comma-joined value.

RubyRails

Suggestions: Array or haystack Hash#

Pass an Array of tags, or a Hash of tag => haystack where the haystack is the synonym text the type-ahead filter matches against — so typing "db" surfaces "Postgres".

# plain list — the filter matches the tag text
f.field :tags, as: :tags, suggestions: %w[Ruby Rails Hotwire]

# haystack Hash — the filter matches the synonyms, the chip shows the tag
f.field :tags, as: :tags, suggestions: {
  "Postgres" => "postgres database db sql",
  "Redis"    => "redis cache kv key value"
}

The model splits the comma-joined value#

The widget submits one comma-joined param (post[tags] = "Ruby,Rails") — the primitive's wire contract. The visible type-ahead input carries no name, so it never posts a stray param; only a hidden field does. Have the model split the string back into an array:

app/models/post.rb
class Post < ApplicationRecord
  attribute :tags, default: []          # a text[] / JSONB column, say

  # accept the widget's "Ruby,Rails" and a normal Array alike
  def tags=(value)
    super(value.is_a?(String) ? value.split(",").map(&:strip).reject(&:empty?) : value)
  end
end

A future release may ship the split writer (or an ActiveModel::Type) so field :tags, as: :tags works against attribute :tags, array: true with zero model code.

Theme parity#

Under the plain theme the widget keeps the entire client wire contract (the tag behavior still works) but ships zero daisyUI classes; the invalid state rides aria-invalid on the query input. Custom styling overrides the leaf's class seams (root_classes, chip_classes, menu_classes, …).

Validating tags in a live form#

A standalone tag widget is its own reactive root, so inside a live form the outer validation root would skip its hidden field. Declare live_tags to lift the widget's wire attributes onto the <form> root and render it rootless — then the form owns the hidden field and :validate runs your real validators over the tags:

app/forms/post_form.rb
class PostForm < Forms::Base
  live model: Post
  live_tags :tags, suggestions: %w[Ruby Rails Hotwire]

  def fields
    field :title
    field :tags, as: :tags     # renders rootless; the form validates it
    submit :primary
  end
end

phlex-reactive's tag controller reads one tag field per root, so a live form lifts at most one — a second live_tags raises. A second tag input must stay a standalone (non-live) field :other, as: :tags.

Constraints#

  • phlex-reactive ≥ 0.11.4 is required — without it the :tags control role isn't registered (the gem still boots and every other field works).
  • Tags can't contain a comma — it's the value separator.
  • One lifted (live-validated) tag field per form; see above.