Tags Input
Multi-value text entry that collects free-form tags, with edit-in-place and array form submission.
Installation
bin/rails g wabi:add tags_inputExample
render Components::UI::TagsInput.new(name: "tags", value: %w[ruby rails]) do
render Components::UI::TagsInputLabel.new { "Tags" }
render Components::UI::TagsInputControl.new do
render Components::UI::TagsInputInput.new(placeholder: "Add a tag…")
end
end
Limiting tags
Cap the number of tags with max:. Pass editable: false to prevent editing existing tags in place.
render Components::UI::TagsInput.new(name: "topics", value: %w[ruby rails], max: 3, editable: false) do
render Components::UI::TagsInputLabel.new { "Topics" }
render Components::UI::TagsInputControl.new do
render Components::UI::TagsInputInput.new(placeholder: "Up to 3 topics…")
end
end
Disabled
render Components::UI::TagsInput.new(name: "tags", value: %w[ruby rails], disabled: true) do
render Components::UI::TagsInputLabel.new { "Tags" }
render Components::UI::TagsInputControl.new do
render Components::UI::TagsInputInput.new
end
end
Source
app/components/ui/tags_input.rb
# frozen_string_literal: true
require "json"
require "date" # Phlex 2.4 references Date/Time constants lazily when rendering data:{} hashes
module Components
module UI
class TagsInput < Wabi::Base
def initialize(name: nil, value: [], max: nil, editable: true, disabled: false, placeholder: nil, **attrs)
@name = name
@value = Array(value)
@max = max
@editable = editable
@disabled = disabled
@placeholder = placeholder
@attrs = attrs
end
def view_template(&block)
user_class = @attrs.delete(:class)
div(
**@attrs,
data: {
controller: "wabi--tags-input",
"wabi--tags-input-name-value": @name,
"wabi--tags-input-value-value": @value.to_json,
"wabi--tags-input-max-value": @max.to_s,
"wabi--tags-input-editable-value": @editable.to_s,
"wabi--tags-input-disabled-value": @disabled.to_s,
"wabi--tags-input-placeholder-value": @placeholder.to_s,
},
class: merge_class("flex flex-col gap-2", user_class)
) do
if block
yield
else
render TagsInputControl.new do
render TagsInputInput.new(placeholder: @placeholder)
end
end
end
end
end
end
end
app/components/ui/tags_input_control.rb
# frozen_string_literal: true
require "date" # Phlex 2.4 references Date/Time constants lazily when rendering data:{} hashes
module Components
module UI
class TagsInputControl < Wabi::Base
def initialize(**attrs)
@attrs = attrs
end
def view_template(&block)
user_class = @attrs.delete(:class)
div(
data: { "wabi--tags-input-target": "control" },
class: merge_class(
"flex min-h-10 w-full flex-wrap items-center gap-1.5 rounded-md border border-input " \
"bg-background px-3 py-2 text-sm ring-offset-background " \
"focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 " \
"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
user_class
)
) do
yield if block_given?
end
end
end
end
end
app/components/ui/tags_input_input.rb
# frozen_string_literal: true
require "date" # Phlex 2.4 references Date/Time constants lazily when rendering data:{} hashes
module Components
module UI
class TagsInputInput < Wabi::Base
def initialize(placeholder: nil, **attrs)
@placeholder = placeholder
@attrs = attrs
end
def view_template
user_class = @attrs.delete(:class)
input(
type: "text",
placeholder: @placeholder,
data: { "wabi--tags-input-target": "input" },
class: merge_class(
"flex-1 min-w-[6rem] bg-transparent outline-none placeholder:text-muted-foreground " \
"disabled:cursor-not-allowed",
user_class
)
)
end
end
end
end
app/components/ui/tags_input_error.rb
# frozen_string_literal: true
require "date" # Phlex 2.4 references Date/Time constants lazily when rendering data:{} hashes
module Components
module UI
class TagsInputError < Wabi::Base
def initialize(**attrs)
@attrs = attrs
end
def view_template(&block)
user_class = @attrs.delete(:class)
p(
role: "alert",
data: { "wabi--tags-input-target": "error" },
class: merge_class("text-sm font-medium text-destructive", user_class)
) do
yield if block_given?
end
end
end
end
end
app/components/ui/tags_input_label.rb
# frozen_string_literal: true
require "date" # Phlex 2.4 references Date/Time constants lazily when rendering data:{} hashes
module Components
module UI
class TagsInputLabel < Wabi::Base
def initialize(**attrs)
@attrs = attrs
end
def view_template(&block)
user_class = @attrs.delete(:class)
label(
data: { "wabi--tags-input-target": "label" },
class: merge_class("text-sm font-medium leading-none", user_class)
) do
yield if block_given?
end
end
end
end
end
Accessibility
- Backspace on an empty input removes the last tag; arrow keys move between tags.
- Each tag exposes a labelled delete button.
- Submits as an array: params[:tags] # => ["ruby", "rails"].
- Disabled tags render with muted text; per WCAG 1.4.3 inactive/disabled UI components are exempt from the contrast minimum.