Input Group
Textarea - Phlex implementation
Examples
Default
Various input group patterns: search with results, URL with tooltip, chat input, and verified username.
<div class="grid w-full max-w-sm gap-6">
<%# 1. Search with results %>
<%= render "ui/input_group" do %>
<%= render "ui/input_group/input", placeholder: "Search..." %>
<%= render "ui/input_group/addon", align: "inline-start" do %>
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
<% end %>
<%= render "ui/input_group/addon", align: "inline-end" do %>12 results<% end %>
<% end %>
<%# 2. URL with tooltip %>
<%= render "ui/input_group" do %>
<%= render "ui/input_group/addon" do %>
<%= render "ui/input_group/text" do %>https://<% end %>
<% end %>
<%= render "ui/input_group/input", placeholder: "example.com", classes: "!pl-1" %>
<%= render "ui/tooltip", as_child: true do |tooltip_attrs| %>
<%= render "ui/input_group/addon", align: "inline-end", attributes: tooltip_attrs do %>
<%= render "ui/tooltip/trigger", as_child: true do |trigger_attrs| %>
<%= render "ui/input_group/button", size: "icon-xs", classes: "rounded-full", attributes: trigger_attrs do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
<% end %>
<% end %>
<%= render "ui/tooltip/content" do %>This is content in a tooltip.<% end %>
<% end %>
<% end %>
<% end %>
<%# 3. Chat/AI input with textarea %>
<%= render "ui/input_group" do %>
<%= render "ui/input_group/textarea", placeholder: "Ask, Search or Chat..." %>
<%= render "ui/input_group/addon", align: "block-end" do %>
<%= render "ui/input_group/button", variant: "outline", size: "icon-xs", classes: "rounded-full" do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
<% end %>
<%= render "ui/dropdown_menu" do %>
<%= render "ui/dropdown_menu/trigger" do %>
<%= render "ui/input_group/button", variant: "ghost" do %>Auto<% end %>
<% end %>
<%= render "ui/dropdown_menu/content", side: "top", align: "start", classes: "[--radius:0.95rem]" do %>
<%= render "ui/dropdown_menu/item" do %>Auto<% end %>
<%= render "ui/dropdown_menu/item" do %>Agent<% end %>
<%= render "ui/dropdown_menu/item" do %>Manual<% end %>
<% end %>
<% end %>
<%= render "ui/input_group/text", classes: "ml-auto" do %>52% used<% end %>
<%= render "ui/separator", orientation: "vertical", classes: "!h-4" %>
<%= render "ui/input_group/button", variant: "default", size: "icon-xs", classes: "rounded-full", attributes: { disabled: true } do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"/></svg>
<span class="sr-only">Send</span>
<% end %>
<% end %>
<% end %>
<%# 4. Verified username %>
<%= render "ui/input_group" do %>
<%= render "ui/input_group/input", placeholder: "@shadcn" %>
<%= render "ui/input_group/addon", align: "inline-end" do %>
<div class="bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full">
<svg class="size-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
</div>
<% end %>
<% end %>
</div><div class="grid w-full max-w-sm gap-6">
<%# 1. Search with results %>
<%= render UI::InputGroup.new do %>
<%= render UI::InputGroupInput.new(placeholder: "Search...") %>
<%= render UI::InputGroupAddon.new(align: "inline-start") do %>
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
<% end %>
<%= render UI::InputGroupAddon.new(align: "inline-end") { "12 results" } %>
<% end %>
<%# 2. URL with tooltip %>
<%= render UI::InputGroup.new do %>
<%= render UI::InputGroupAddon.new do %>
<%= render UI::InputGroupText.new { "https://" } %>
<% end %>
<%= render UI::InputGroupInput.new(placeholder: "example.com", classes: "!pl-1") %>
<%= render UI::Tooltip.new(as_child: true) do |tooltip_attrs| %>
<%= render UI::InputGroupAddon.new(align: "inline-end", **tooltip_attrs) do %>
<%= render UI::TooltipTrigger.new(as_child: true) do |trigger_attrs| %>
<%= render UI::InputGroupButton.new(size: "icon-xs", classes: "rounded-full", **trigger_attrs) do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
<% end %>
<% end %>
<%= render UI::TooltipContent.new { "This is content in a tooltip." } %>
<% end %>
<% end %>
<% end %>
<%# 3. Chat/AI input with textarea %>
<%= render UI::InputGroup.new do %>
<%= render UI::InputGroupTextarea.new(placeholder: "Ask, Search or Chat...") %>
<%= render UI::InputGroupAddon.new(align: "block-end") do %>
<%= render UI::InputGroupButton.new(variant: "outline", size: "icon-xs", classes: "rounded-full") do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
<% end %>
<%= render UI::DropdownMenu.new do %>
<%= render UI::DropdownMenuTrigger.new do %>
<%= render UI::InputGroupButton.new(variant: "ghost") { "Auto" } %>
<% end %>
<%= render UI::DropdownMenuContent.new(side: "top", align: "start", classes: "[--radius:0.95rem]") do %>
<%= render UI::DropdownMenuItem.new { "Auto" } %>
<%= render UI::DropdownMenuItem.new { "Agent" } %>
<%= render UI::DropdownMenuItem.new { "Manual" } %>
<% end %>
<% end %>
<%= render UI::InputGroupText.new(classes: "ml-auto") { "52% used" } %>
<%= render UI::Separator.new(orientation: "vertical", classes: "!h-4") %>
<%= render UI::InputGroupButton.new(variant: "default", size: "icon-xs", classes: "rounded-full", disabled: true) do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"/></svg>
<span class="sr-only">Send</span>
<% end %>
<% end %>
<% end %>
<%# 4. Verified username %>
<%= render UI::InputGroup.new do %>
<%= render UI::InputGroupInput.new(placeholder: "@shadcn") %>
<%= render UI::InputGroupAddon.new(align: "inline-end") do %>
<div class="bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full">
<svg class="size-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
</div>
<% end %>
<% end %>
</div><div class="grid w-full max-w-sm gap-6">
<%# 1. Search with results %>
<%= render UI::InputGroupComponent.new do %>
<%= render UI::InputGroupInputComponent.new(placeholder: "Search...") %>
<%= render UI::InputGroupAddonComponent.new(align: "inline-start") do %>
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
<% end %>
<%= render(UI::InputGroupAddonComponent.new(align: "inline-end")) { "12 results" } %>
<% end %>
<%# 2. URL with tooltip %>
<%= render UI::InputGroupComponent.new do %>
<%= render UI::InputGroupAddonComponent.new do %>
<%= render(UI::InputGroupTextComponent.new) { "https://" } %>
<% end %>
<%= render UI::InputGroupInputComponent.new(placeholder: "example.com", classes: "!pl-1") %>
<%= render UI::TooltipComponent.new(as_child: true) do |tooltip| %>
<%= render UI::InputGroupAddonComponent.new(align: "inline-end", **tooltip.tooltip_attrs) do %>
<%= render UI::TooltipTriggerComponent.new(as_child: true) do |trigger| %>
<%= render UI::InputGroupButtonComponent.new(size: "icon-xs", classes: "rounded-full", **trigger.trigger_attrs) do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
<% end %>
<% end %>
<%= render(UI::TooltipContentComponent.new) { "This is content in a tooltip." } %>
<% end %>
<% end %>
<% end %>
<%# 3. Chat/AI input with textarea %>
<%= render UI::InputGroupComponent.new do %>
<%= render UI::InputGroupTextareaComponent.new(placeholder: "Ask, Search or Chat...") %>
<%= render UI::InputGroupAddonComponent.new(align: "block-end") do %>
<%= render UI::InputGroupButtonComponent.new(variant: "outline", size: "icon-xs", classes: "rounded-full") do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
<% end %>
<%= render UI::DropdownMenuComponent.new do %>
<%= render UI::DropdownMenuTriggerComponent.new do %>
<%= render(UI::InputGroupButtonComponent.new(variant: "ghost")) { "Auto" } %>
<% end %>
<%= render UI::DropdownMenuContentComponent.new(side: "top", align: "start", classes: "[--radius:0.95rem]") do %>
<%= render(UI::DropdownMenuItemComponent.new) { "Auto" } %>
<%= render(UI::DropdownMenuItemComponent.new) { "Agent" } %>
<%= render(UI::DropdownMenuItemComponent.new) { "Manual" } %>
<% end %>
<% end %>
<%= render(UI::InputGroupTextComponent.new(classes: "ml-auto")) { "52% used" } %>
<%= render UI::SeparatorComponent.new(orientation: "vertical", classes: "!h-4") %>
<%= render UI::InputGroupButtonComponent.new(variant: "default", size: "icon-xs", classes: "rounded-full", disabled: true) do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"/></svg>
<span class="sr-only">Send</span>
<% end %>
<% end %>
<% end %>
<%# 4. Verified username %>
<%= render UI::InputGroupComponent.new do %>
<%= render UI::InputGroupInputComponent.new(placeholder: "@shadcn") %>
<%= render UI::InputGroupAddonComponent.new(align: "inline-end") do %>
<div class="bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full">
<svg class="size-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
</div>
<% end %>
<% end %>
</div>With Icon Addon
Input with an icon prefix.
<%= render "ui/input_group" do %>
<%= render "ui/input_group/addon", align: "inline-start" do %>
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<% end %>
<%= render "ui/input_group/input", placeholder: "Search..." %>
<% end %><%= render UI::InputGroup.new do %>
<%= render UI::InputGroupAddon.new(align: "inline-start") do %>
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<% end %>
<%= render UI::InputGroupInput.new(placeholder: "Search...") %>
<% end %><%= render UI::InputGroupComponent.new do %>
<%= render UI::InputGroupAddonComponent.new(align: "inline-start") do %>
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<% end %>
<%= render UI::InputGroupInputComponent.new(placeholder: "Search...") %>
<% end %>Text Addon
Input with text prefix and suffix.
<%= render "ui/input_group" do %>
<%= render "ui/input_group/addon", align: "inline-start" do %>
<%= render "ui/input_group/text", content: "$" %>
<% end %>
<%= render "ui/input_group/input", type: "number", placeholder: "0.00" %>
<%= render "ui/input_group/addon", align: "inline-end" do %>
<%= render "ui/input_group/text", content: "USD" %>
<% end %>
<% end %><%= render UI::InputGroup.new do %>
<%= render UI::InputGroupAddon.new(align: "inline-start") do %>
<%= render UI::InputGroupText.new { "$" } %>
<% end %>
<%= render UI::InputGroupInput.new(type: "number", placeholder: "0.00") %>
<%= render UI::InputGroupAddon.new(align: "inline-end") do %>
<%= render UI::InputGroupText.new { "USD" } %>
<% end %>
<% end %><%= render UI::InputGroupComponent.new do %>
<%= render UI::InputGroupAddonComponent.new(align: "inline-start") do %>
<%= render(UI::InputGroupTextComponent.new) { "$" } %>
<% end %>
<%= render UI::InputGroupInputComponent.new(type: "number", placeholder: "0.00") %>
<%= render UI::InputGroupAddonComponent.new(align: "inline-end") do %>
<%= render(UI::InputGroupTextComponent.new) { "USD" } %>
<% end %>
<% end %>With Tooltip
Input with a tooltip for additional context.
<%= render "ui/input_group" do %>
<%= render "ui/input_group/input", placeholder: "Enter password", type: "password" %>
<%= render "ui/input_group/addon", align: "inline-end" do %>
<%= render "ui/tooltip" do %>
<%= render "ui/tooltip/trigger", attributes: { class: "inline-flex items-center justify-center border-0 bg-transparent p-0 shadow-none hover:bg-transparent focus:ring-0" } do %>
<svg class="size-4 text-muted-foreground hover:text-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<% end %>
<%= render "ui/tooltip/content" do %>Password must be at least 8 characters<% end %>
<% end %>
<% end %>
<% end %><%= render UI::InputGroup.new do %>
<%= render UI::InputGroupInput.new(placeholder: "Enter password", type: "password") %>
<%= render UI::InputGroupAddon.new(align: "inline-end") do %>
<%= render UI::Tooltip.new do %>
<%= render UI::TooltipTrigger.new(class: "inline-flex items-center justify-center border-0 bg-transparent p-0 shadow-none hover:bg-transparent focus:ring-0") do %>
<svg class="size-4 text-muted-foreground hover:text-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<% end %>
<%= render UI::TooltipContent.new { "Password must be at least 8 characters" } %>
<% end %>
<% end %>
<% end %><%= render UI::InputGroupComponent.new do %>
<%= render UI::InputGroupInputComponent.new(placeholder: "Enter password", type: "password") %>
<%= render UI::InputGroupAddonComponent.new(align: "inline-end") do %>
<%= render UI::TooltipComponent.new do %>
<%= render UI::TooltipTriggerComponent.new(class: "inline-flex items-center justify-center border-0 bg-transparent p-0 shadow-none hover:bg-transparent focus:ring-0") do %>
<svg class="size-4 text-muted-foreground hover:text-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<% end %>
<%= render(UI::TooltipContentComponent.new) { "Password must be at least 8 characters" } %>
<% end %>
<% end %>
<% end %>Textarea
A mini code editor with header and footer using block-start and block-end alignments.
script.js
Line 1, Column 1
<%= render "ui/input_group", classes: "w-full max-w-md" do %>
<%# Header with filename and actions %>
<%= render "ui/input_group/addon", align: "block-start", classes: "border-b" do %>
<%= render "ui/input_group/text", classes: "font-mono font-medium gap-1.5" do %>
<svg class="size-4 text-gray-400" viewBox="0 0 24 24" fill="currentColor"><path d="M3 3h18v18H3V3m4.73 15.04c.4.85 1.19 1.55 2.54 1.55 1.5 0 2.53-.8 2.53-2.55v-5.78h-1.7V17c0 .86-.35 1.08-.9 1.08-.58 0-.82-.4-1.09-.87l-1.38.83m5.98-.18c.5.98 1.51 1.73 3.09 1.73 1.6 0 2.8-.83 2.8-2.36 0-1.41-.81-2.04-2.25-2.66l-.42-.18c-.73-.31-1.04-.52-1.04-1.02 0-.41.31-.73.81-.73.48 0 .8.21 1.09.73l1.31-.87c-.55-.96-1.33-1.33-2.4-1.33-1.51 0-2.48.96-2.48 2.23 0 1.38.81 2.03 2.03 2.55l.42.18c.78.34 1.24.55 1.24 1.13 0 .48-.45.83-1.15.83-.83 0-1.31-.43-1.67-1.03l-1.38.8z"/></svg>
script.js
<% end %>
<%= render "ui/input_group/button", size: "icon-xs", classes: "ml-auto", attributes: { title: "Refresh" } do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
<% end %>
<%= render "ui/input_group/button", size: "icon-xs", attributes: { title: "Copy" } do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>
<% end %>
<% end %>
<%# Code textarea %>
<%= render "ui/input_group/textarea", placeholder: "console.log('Hello, world!');", classes: "min-h-[200px] font-mono text-sm", attributes: { rows: 8 } %>
<%# Footer with line info and run button %>
<%= render "ui/input_group/addon", align: "block-end", classes: "border-t" do %>
<%= render "ui/input_group/text" do %>Line 1, Column 1<% end %>
<%= render "ui/input_group/button", size: "sm", variant: "default", classes: "ml-auto gap-1" do %>
Run
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5"/></svg>
<% end %>
<% end %>
<% end %>
script.js
Line 1, Column 1
<%= render UI::InputGroup.new(classes: "w-full max-w-md") do %>
<%# Header with filename and actions %>
<%= render UI::InputGroupAddon.new(align: "block-start", classes: "border-b") do %>
<%= render UI::InputGroupText.new(classes: "font-mono font-medium gap-1.5") do %>
<svg class="size-4 text-gray-400" viewBox="0 0 24 24" fill="currentColor"><path d="M3 3h18v18H3V3m4.73 15.04c.4.85 1.19 1.55 2.54 1.55 1.5 0 2.53-.8 2.53-2.55v-5.78h-1.7V17c0 .86-.35 1.08-.9 1.08-.58 0-.82-.4-1.09-.87l-1.38.83m5.98-.18c.5.98 1.51 1.73 3.09 1.73 1.6 0 2.8-.83 2.8-2.36 0-1.41-.81-2.04-2.25-2.66l-.42-.18c-.73-.31-1.04-.52-1.04-1.02 0-.41.31-.73.81-.73.48 0 .8.21 1.09.73l1.31-.87c-.55-.96-1.33-1.33-2.4-1.33-1.51 0-2.48.96-2.48 2.23 0 1.38.81 2.03 2.03 2.55l.42.18c.78.34 1.24.55 1.24 1.13 0 .48-.45.83-1.15.83-.83 0-1.31-.43-1.67-1.03l-1.38.8z"/></svg>
script.js
<% end %>
<%= render UI::InputGroupButton.new(size: "icon-xs", classes: "ml-auto", title: "Refresh") do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
<% end %>
<%= render UI::InputGroupButton.new(size: "icon-xs", title: "Copy") do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>
<% end %>
<% end %>
<%# Code textarea %>
<%= render UI::InputGroupTextarea.new(placeholder: "console.log('Hello, world!');", classes: "min-h-[200px] font-mono text-sm", rows: 8) %>
<%# Footer with line info and run button %>
<%= render UI::InputGroupAddon.new(align: "block-end", classes: "border-t") do %>
<%= render UI::InputGroupText.new { "Line 1, Column 1" } %>
<%= render UI::InputGroupButton.new(size: "sm", variant: "default", classes: "ml-auto gap-1") do %>
Run
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5"/></svg>
<% end %>
<% end %>
<% end %>
script.js
Line 1, Column 1
<%= render UI::InputGroupComponent.new(classes: "w-full max-w-md") do %>
<%# Header with filename and actions %>
<%= render UI::InputGroupAddonComponent.new(align: "block-start", classes: "border-b") do %>
<%= render UI::InputGroupTextComponent.new(classes: "font-mono font-medium gap-1.5") do %>
<svg class="size-4 text-gray-400" viewBox="0 0 24 24" fill="currentColor"><path d="M3 3h18v18H3V3m4.73 15.04c.4.85 1.19 1.55 2.54 1.55 1.5 0 2.53-.8 2.53-2.55v-5.78h-1.7V17c0 .86-.35 1.08-.9 1.08-.58 0-.82-.4-1.09-.87l-1.38.83m5.98-.18c.5.98 1.51 1.73 3.09 1.73 1.6 0 2.8-.83 2.8-2.36 0-1.41-.81-2.04-2.25-2.66l-.42-.18c-.73-.31-1.04-.52-1.04-1.02 0-.41.31-.73.81-.73.48 0 .8.21 1.09.73l1.31-.87c-.55-.96-1.33-1.33-2.4-1.33-1.51 0-2.48.96-2.48 2.23 0 1.38.81 2.03 2.03 2.55l.42.18c.78.34 1.24.55 1.24 1.13 0 .48-.45.83-1.15.83-.83 0-1.31-.43-1.67-1.03l-1.38.8z"/></svg>
script.js
<% end %>
<%= render UI::InputGroupButtonComponent.new(size: "icon-xs", classes: "ml-auto", title: "Refresh") do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
<% end %>
<%= render UI::InputGroupButtonComponent.new(size: "icon-xs", title: "Copy") do %>
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>
<% end %>
<% end %>
<%# Code textarea %>
<%= render UI::InputGroupTextareaComponent.new(placeholder: "console.log('Hello, world!');", classes: "min-h-[200px] font-mono text-sm", rows: 8) %>
<%# Footer with line info and run button %>
<%= render UI::InputGroupAddonComponent.new(align: "block-end", classes: "border-t") do %>
<%= render(UI::InputGroupTextComponent.new) { "Line 1, Column 1" } %>
<%= render UI::InputGroupButtonComponent.new(size: "sm", variant: "default", classes: "ml-auto gap-1") do %>
Run
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5"/></svg>
<% end %>
<% end %>
<% end %>Spinner
Input with a loading spinner.
<%= render "ui/input_group" do %>
<%= render "ui/input_group/input", placeholder: "Loading..." %>
<%= render "ui/input_group/addon", align: "inline-end" do %>
<%= render "ui/spinner", size: "sm" %>
<% end %>
<% end %><%= render UI::InputGroup.new do %>
<%= render UI::InputGroupInput.new(placeholder: "Loading...") %>
<%= render UI::InputGroupAddon.new(align: "inline-end") do %>
<%= render UI::Spinner.new(size: "sm") %>
<% end %>
<% end %><%= render UI::InputGroupComponent.new do %>
<%= render UI::InputGroupInputComponent.new(placeholder: "Loading...") %>
<%= render UI::InputGroupAddonComponent.new(align: "inline-end") do %>
<%= render UI::SpinnerComponent.new(size: "sm") %>
<% end %>
<% end %>With Label
Input group with an integrated label.
<%= render "ui/input_group" do %>
<%= render "ui/input_group/addon", align: "inline-start" do %>
<%= render "ui/label", for: "website", classes: "text-sm font-medium" do %>Website<% end %>
<% end %>
<%= render "ui/input_group/input", id: "website", placeholder: "example.com" %>
<% end %><%= render UI::InputGroup.new do %>
<%= render UI::InputGroupAddon.new(align: "inline-start") do %>
<%= render UI::Label.new(for: "website", classes: "text-sm font-medium") { "Website" } %>
<% end %>
<%= render UI::InputGroupInput.new(id: "website", placeholder: "example.com") %>
<% end %><%= render UI::InputGroupComponent.new do %>
<%= render UI::InputGroupAddonComponent.new(align: "inline-start") do %>
<%= render(UI::LabelComponent.new(for: "website", classes: "text-sm font-medium")) { "Website" } %>
<% end %>
<%= render UI::InputGroupInputComponent.new(id: "website", placeholder: "example.com") %>
<% end %>Dropdown
Input group with a dropdown menu.
<%= render "ui/input_group" do %>
<%= render "ui/input_group/input", placeholder: "Enter file name" %>
<%= render "ui/input_group/addon", align: "inline-end" do %>
<%= render "ui/dropdown_menu" do %>
<%= render "ui/dropdown_menu/trigger" do %>
<%= render "ui/input_group/button", variant: "ghost", size: "icon-xs" do %>
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h.01M12 12h.01M19 12h.01M6 12a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0z"/>
</svg>
<% end %>
<% end %>
<%= render "ui/dropdown_menu/content", align: "end" do %>
<%= render "ui/dropdown_menu/item" do %>Settings<% end %>
<%= render "ui/dropdown_menu/item" do %>Copy path<% end %>
<%= render "ui/dropdown_menu/item" do %>Open location<% end %>
<% end %>
<% end %>
<% end %>
<% end %><%= render UI::InputGroup.new do %>
<%= render UI::InputGroupInput.new(placeholder: "Enter file name") %>
<%= render UI::InputGroupAddon.new(align: "inline-end") do %>
<%= render UI::DropdownMenu.new do %>
<%= render UI::DropdownMenuTrigger.new do %>
<%= render UI::InputGroupButton.new(variant: "ghost", size: "icon-xs") do %>
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h.01M12 12h.01M19 12h.01M6 12a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0z"/>
</svg>
<% end %>
<% end %>
<%= render UI::DropdownMenuContent.new(align: "end") do %>
<%= render UI::DropdownMenuItem.new { "Settings" } %>
<%= render UI::DropdownMenuItem.new { "Copy path" } %>
<%= render UI::DropdownMenuItem.new { "Open location" } %>
<% end %>
<% end %>
<% end %>
<% end %><%= render UI::InputGroupComponent.new do %>
<%= render UI::InputGroupInputComponent.new(placeholder: "Enter file name") %>
<%= render UI::InputGroupAddonComponent.new(align: "inline-end") do %>
<%= render UI::DropdownMenuComponent.new do %>
<%= render UI::DropdownMenuTriggerComponent.new do %>
<%= render UI::InputGroupButtonComponent.new(variant: "ghost", size: "icon-xs") do %>
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h.01M12 12h.01M19 12h.01M6 12a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0z"/>
</svg>
<% end %>
<% end %>
<%= render UI::DropdownMenuContentComponent.new(align: "end") do %>
<%= render(UI::DropdownMenuItemComponent.new) { "Settings" } %>
<%= render(UI::DropdownMenuItemComponent.new) { "Copy path" } %>
<%= render(UI::DropdownMenuItemComponent.new) { "Open location" } %>
<% end %>
<% end %>
<% end %>
<% end %>Dropdown Rounded
Input group with custom radius and dropdown trigger.
<%= render "ui/input_group", classes: "[--radius:1rem]" do %>
<%= render "ui/input_group/input", placeholder: "Enter search query" %>
<%= render "ui/dropdown_menu", as_child: true do |dropdown_attrs| %>
<%= render "ui/input_group/addon", align: "inline-end", attributes: dropdown_attrs do %>
<%= render "ui/dropdown_menu/trigger", as_child: true do |trigger_attrs| %>
<%= render "ui/input_group/button", variant: "ghost", classes: "!pr-1.5 text-xs gap-1", attributes: trigger_attrs do %>
Search In...
<svg class="size-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
<% end %>
<% end %>
<%= render "ui/dropdown_menu/content", align: "end", classes: "[--radius:0.95rem]" do %>
<%= render "ui/dropdown_menu/item" do %>Documentation<% end %>
<%= render "ui/dropdown_menu/item" do %>Blog Posts<% end %>
<%= render "ui/dropdown_menu/item" do %>Changelog<% end %>
<% end %>
<% end %>
<% end %>
<% end %><%= render UI::InputGroup.new(classes: "[--radius:1rem]") do %>
<%= render UI::InputGroupInput.new(placeholder: "Enter search query") %>
<%= render UI::DropdownMenu.new(as_child: true) do |dropdown_attrs| %>
<%= render UI::InputGroupAddon.new(align: "inline-end", **dropdown_attrs) do %>
<%= render UI::DropdownMenuTrigger.new(as_child: true) do |trigger_attrs| %>
<%= render UI::InputGroupButton.new(variant: "ghost", classes: "!pr-1.5 text-xs gap-1", **trigger_attrs) do %>
Search In...
<svg class="size-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
<% end %>
<% end %>
<%= render UI::DropdownMenuContent.new(align: "end", classes: "[--radius:0.95rem]") do %>
<%= render UI::DropdownMenuItem.new { "Documentation" } %>
<%= render UI::DropdownMenuItem.new { "Blog Posts" } %>
<%= render UI::DropdownMenuItem.new { "Changelog" } %>
<% end %>
<% end %>
<% end %>
<% end %><%= render UI::InputGroupComponent.new(classes: "[--radius:1rem]") do %>
<%= render UI::InputGroupInputComponent.new(placeholder: "Enter search query") %>
<%= render UI::DropdownMenuComponent.new(as_child: true) do |dropdown| %>
<%= render UI::InputGroupAddonComponent.new(align: "inline-end", **dropdown.dropdown_attrs) do %>
<%= render UI::DropdownMenuTriggerComponent.new(as_child: true) do |trigger| %>
<%= render UI::InputGroupButtonComponent.new(variant: "ghost", classes: "!pr-1.5 text-xs gap-1", **trigger.trigger_attrs) do %>
Search In...
<svg class="size-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
<% end %>
<% end %>
<%= render UI::DropdownMenuContentComponent.new(align: "end", classes: "[--radius:0.95rem]") do %>
<%= render(UI::DropdownMenuItemComponent.new) { "Documentation" } %>
<%= render(UI::DropdownMenuItemComponent.new) { "Blog Posts" } %>
<%= render(UI::DropdownMenuItemComponent.new) { "Changelog" } %>
<% end %>
<% end %>
<% end %>
<% end %>Features
- Custom styling with Tailwind classes
API Reference
Input Group Textarea
A textarea element styled for use within input groups.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
| placeholder | String | nil | Placeholder text when no value is selected |
| value | String | nil | The current value |
| name | String | nil | Form field name |
| id | String | nil | HTML id attribute |
| rows | String | nil | The rows |