---
templateEngineOverride: njk
layout: layouts/page.njk
title: Input Group
description: Add icons, text, buttons, and helper content to inputs and textareas.
toc:
- label: Usage
id: usage
- label: Examples
id: examples
children:
- label: Align
id: example-align
children:
- label: inline-start
id: align-inline-start
- label: inline-end
id: align-inline-end
- label: block-start
id: align-block-start
- label: block-end
id: align-block-end
- label: Icon
id: example-icon
- label: Text
id: example-text
- label: Button
id: example-button
- label: Kbd
id: example-kbd
- label: Dropdown
id: example-dropdown
- label: Spinner
id: example-spinner
- label: Textarea
id: example-textarea
- label: Custom Input
id: example-custom-input
- label: RTL
id: example-rtl
---
{% from "macros/code_block.njk" import code_block %}
{% from "macros/code_preview.njk" import code_preview %}
{% from "dropdown-menu.njk" import dropdown_menu %}
{% set spinner_icon %}{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}{% endset %}
{% set code_default %}
{% lucide "search" %}
12 results
https://
{% lucide "info" %}
{% lucide "plus" %}
{% set trigger %}Auto{% endset %}
{{ dropdown_menu(
main_attrs={"class": "min-w-0"},
trigger=trigger,
trigger_attrs={"class": "btn-xs-ghost"},
popover_attrs={"data-side": "top", "data-align": "start", "class": "min-w-28 [--radius:0.95rem]"},
items=[
{ type: "item", label: "Auto" },
{ type: "item", label: "Agent" },
{ type: "item", label: "Manual" }
]
) }}
52% used
{% lucide "arrow-up" %}
{% lucide "check", { "class": "size-3" } %}
{% endset %}
{{ code_preview("input-group-default", code_default | prettyHtml, class="w-full max-w-sm") }}
{% set code_usage %}
12 results
{% endset %}
{{ code_block(code_usage | prettyHtml, "html") }}
Use .input-group on the root. The group owns the visible shell. Native controls stay semantic and become borderless children inside that shell.
The component has the following HTML structure:
<div class="input-group">
The root shell. Add role="group" and an accessible label only when the group itself needs to be named.
<input>, <textarea>, <select>
The native control. Use aria-invalid="true" for invalid state and disabled for disabled state.
<span>, <svg>, <kbd>
Decorative or helper content. Use aria-hidden="true" for decorative icons.
<button>, <div role="group">
Interactive actions. Use role="group" with an accessible label when multiple related actions share one side of the input.
<header>, <footer>
Header or footer addon rows for vertically oriented input groups.
data-align="start|end"
Optional visual placement. Values use logical direction, so start/end automatically flip in RTL. Use data-orientation="vertical" on the root for header/footer layouts.
[data-control]
Escape hatch for custom controls that are not native input, textarea, or select.
Use data-align="start|end" on addon wrappers to position them inline. Use data-orientation="vertical" with header or footer for block placement.
{% set code_align_inline_start %}
{% endset %}
{{ code_preview("input-group-inline-start", code_align_inline_start | prettyHtml, class="w-full max-w-sm") }}
{% set code_align_inline_end %}
{% endset %}
{{ code_preview("input-group-inline-end", code_align_inline_end | prettyHtml, class="w-full max-w-sm") }}
{% set code_align_block_start %}
Textarea
{% lucide "file-code", { "class": "text-muted-foreground" } %}
script.js
{% lucide "copy" %}
Header positioned above the textarea.
{% endset %}
{{ code_preview("input-group-block-start", code_align_block_start | prettyHtml, class="w-full max-w-sm") }}
{% set code_align_block_end %}
Textarea
Footer positioned below the textarea.
{% endset %}
{{ code_preview("input-group-block-end", code_align_block_end | prettyHtml, class="w-full max-w-sm") }}
{% set code_icon %}
{% endset %}
{{ code_preview("input-group-icon", code_icon | prettyHtml, class="w-full max-w-sm") }}
{% set code_text %}
{% endset %}
{{ code_preview("input-group-text", code_text | prettyHtml, class="w-full max-w-sm") }}
{% set code_button %}
{% endset %}
{{ code_preview("input-group-button", code_button | prettyHtml, class="w-full max-w-sm") }}
{% set code_kbd %}
{% lucide "search" %}
⌘K
{% endset %}
{{ code_preview("input-group-kbd", code_kbd | prettyHtml, class="w-full max-w-sm") }}
{% set code_dropdown %}
{% set trigger %}{% lucide "ellipsis" %}{% endset %}
{{ dropdown_menu(
main_attrs={"data-align": "end"},
trigger=trigger,
trigger_attrs={"class": "btn-xs-icon-ghost", "aria-label": "Open file actions"},
popover_attrs={"data-align": "end", "class": "min-w-32"},
items=[
{ type: "item", label: "Settings" },
{ type: "item", label: "Copy path" },
{ type: "item", label: "Open location" }
]
) }}
{% set trigger %}Search In... {% lucide "chevron-down", { "class": "size-3" } %}{% endset %}
{{ dropdown_menu(
main_attrs={"data-align": "end"},
trigger=trigger,
trigger_attrs={"class": "btn-xs-ghost pr-1.5! text-xs"},
popover_attrs={"data-align": "end", "class": "min-w-32 [--radius:0.95rem]"},
items=[
{ type: "item", label: "Documentation" },
{ type: "item", label: "Blog Posts" },
{ type: "item", label: "Changelog" }
]
) }}
{% endset %}
{{ code_preview("input-group-dropdown", code_dropdown | prettyHtml, class="w-full max-w-sm") }}
{% set code_spinner %}
{% endset %}
{{ code_preview("input-group-spinner", code_spinner | prettyHtml, class="w-full max-w-sm") }}
{% set code_textarea %}
{% endset %}
{{ code_preview("input-group-textarea", code_textarea | prettyHtml, class="w-full max-w-md") }}
{% set code_custom %}
{% endset %}
{{ code_preview("input-group-custom", code_custom | prettyHtml, class="w-full max-w-sm") }}
{% set code_rtl %}
{% endset %}
{{ code_preview("input-group-rtl", code_rtl | prettyHtml, class="w-full max-w-sm") }}