New components library - Native UI

Input Group

Add addons, buttons, and helper content to inputs.

Installation

Copy the following code into your app directory.

uv

uv run buridan add component input_group
from components.ui.input_group import input_group

Anatomy

Use the following composition to build an Input Group component.

input_group.root( input_group.input(placeholder="Search..."), input_group.addon(), )

Align

Use the align prop on input_group.addon to position the addon relative to the input.

For proper focus management, input_group.addon should always be placed after input_group.input or input_group.textarea in the DOM. Use the align prop to visually position the addon.

inline-start

Use align="inline-start" to position the addon at the start of the input. This is the default.

import reflex as rx

from components.icons.hugeicon import hi
from components.ui.input_group import input_group


def input_group_inline_start() -> rx.Component:
    return input_group.root(
        input_group.input(
            id="inline-start-input",
            placeholder="Search...",
        ),
        input_group.addon(
            hi("SearchIcon", class_name="text-muted-foreground"),
            align="inline-start",
        ),
        class_name="max-w-sm",
    )

inline-end

Use align="inline-end" to position the addon at the end of the input.

import reflex as rx

from components.icons.hugeicon import hi
from components.ui.input_group import input_group


def input_group_inline_end() -> rx.Component:
    return input_group.root(
        input_group.input(
            id="inline-end-input",
            type="password",
            placeholder="Enter password",
        ),
        input_group.addon(
            hi("EyeOffIcon", class_name="text-muted-foreground"),
            align="inline-end",
        ),
        class_name="max-w-sm",
    )

block-start

Use align="block-start" to position the addon above the input.

Full Name

Header positioned above the input.

script.py

Header positioned above the textarea.

import reflex as rx

from components.icons.hugeicon import hi
from components.ui.field import field
from components.ui.input_group import input_group


def input_group_block_start() -> rx.Component:
    return rx.el.div(
        field.root(
            field.label("Input", html_for="block-start-input"),
            field.content(
                input_group.root(
                    input_group.input(
                        id="block-start-input",
                        placeholder="Enter your name",
                    ),
                    input_group.addon(
                        input_group.text("Full Name"),
                        align="block-start",
                    ),
                    class_name="h-auto",
                )
            ),
            field.description("Header positioned above the input."),
            orientation="vertical",
        ),
        field.root(
            field.label("Textarea", html_for="block-start-textarea"),
            field.content(
                input_group.root(
                    input_group.textarea(
                        id="block-start-textarea",
                        placeholder="console.log('Hello, world!');",
                        class_name="font-mono text-sm",
                    ),
                    input_group.addon(
                        hi("FileCodeIcon", class_name="text-muted-foreground"),
                        input_group.text("script.py", class_name="font-mono"),
                        input_group.button(
                            hi("CopyIcon"),
                            rx.el.span("Copy", class_name="sr-only"),
                            size="icon-xs",
                            class_name="ml-auto",
                        ),
                        align="block-start",
                    ),
                )
            ),
            field.description("Header positioned above the textarea."),
            orientation="vertical",
        ),
        class_name="flex flex-col gap-y-6 max-w-sm w-full",
    )

block-end

Use align="block-end" to position the addon below the input.

USD

Footer positioned below the input.

0/280

Footer positioned below the textarea.

import reflex as rx

from components.ui.field import field
from components.ui.input_group import input_group


def input_group_block_end() -> rx.Component:
    return rx.el.div(
        field.root(
            field.label("Input", html_for="block-end-input"),
            field.content(
                input_group.root(
                    input_group.input(
                        id="block-end-input",
                        placeholder="Enter amount",
                    ),
                    input_group.addon(
                        input_group.text("USD"),
                        align="block-end",
                    ),
                    class_name="h-auto",
                )
            ),
            field.description("Footer positioned below the input."),
            orientation="vertical",
        ),
        field.root(
            field.label("Textarea", html_for="block-end-textarea"),
            field.content(
                input_group.root(
                    input_group.textarea(
                        id="block-end-textarea",
                        placeholder="Write a comment...",
                    ),
                    input_group.addon(
                        input_group.text("0/280"),
                        input_group.button(
                            "Post",
                            variant="default",
                            size="sm",
                            class_name="ml-auto",
                        ),
                        align="block-end",
                    ),
                )
            ),
            field.description("Footer positioned below the textarea."),
            orientation="vertical",
        ),
        class_name="flex flex-col gap-y-6 max-w-sm w-full",
    )
import reflex as rx

from components.icons.hugeicon import hi
from components.ui.input_group import input_group


def input_group_icons() -> rx.Component:
    return rx.el.div(
        input_group.root(
            input_group.input(placeholder="Search..."),
            input_group.addon(
                hi("Search01Icon", class_name="text-muted-foreground"),
                align="inline-start",
            ),
        ),
        input_group.root(
            input_group.input(type="email", placeholder="Enter your email"),
            input_group.addon(
                hi("Mail01Icon", class_name="text-muted-foreground"),
                align="inline-start",
            ),
        ),
        input_group.root(
            input_group.input(placeholder="Card number"),
            input_group.addon(
                hi("CreditCardIcon", class_name="text-muted-foreground"),
                align="inline-start",
            ),
            input_group.addon(
                hi("Tick02Icon", class_name="text-muted-foreground"),
                align="inline-end",
            ),
        ),
        input_group.root(
            input_group.input(placeholder="Card number"),
            input_group.addon(
                hi("StarIcon", class_name="text-muted-foreground"),
                hi("InformationCircleIcon", class_name="text-muted-foreground"),
                align="inline-end",
            ),
        ),
        class_name="grid w-full max-w-sm gap-6",
    )
$
USD
https://
.com
@company.com
120 characters left
import reflex as rx

from components.ui.input_group import input_group


def input_group_text() -> rx.Component:
    return rx.el.div(
        input_group.root(
            input_group.addon(
                input_group.text("$"),
                align="inline-start",
            ),
            input_group.input(placeholder="0.00"),
            input_group.addon(
                input_group.text("USD"),
                align="inline-end",
            ),
        ),
        input_group.root(
            input_group.addon(
                input_group.text("https://"),
                align="inline-start",
            ),
            input_group.input(
                placeholder="example.com",
                class_name="!pl-0.5",
            ),
            input_group.addon(
                input_group.text(".com"),
                align="inline-end",
            ),
        ),
        input_group.root(
            input_group.input(placeholder="Enter your username"),
            input_group.addon(
                input_group.text("@company.com"),
                align="inline-end",
            ),
        ),
        input_group.root(
            input_group.textarea(placeholder="Enter your message"),
            input_group.addon(
                input_group.text(
                    "120 characters left",
                    class_name="text-xs text-muted-foreground",
                ),
                align="block-end",
            ),
        ),
        class_name="grid w-full max-w-sm gap-6",
    )
https://
import reflex as rx
from reflex.experimental import ClientStateVar

from components.icons.hugeicon import hi
from components.ui.input_group import input_group

_input_copy = ClientStateVar.create("_input_copy", False)
_input_star = ClientStateVar.create("_input_star", False)


def input_group_button() -> rx.Component:
    return rx.el.div(
        input_group.root(
            input_group.input(
                placeholder="https://ui.buridan.dev/",
                read_only=True,
            ),
            input_group.addon(
                input_group.button(
                    rx.cond(
                        _input_copy.value,
                        hi("Tick02Icon"),
                        hi("Copy01Icon"),
                    ),
                    aria_label="Copy",
                    title="Copy",
                    size="icon-xs",
                    on_click=[
                        _input_copy.set_value(True),
                        rx.set_clipboard("https://ui.buridan.dev/"),
                    ],
                    on_mouse_down=rx.call_function(
                        _input_copy.set_value(False)
                    ).debounce(1500),
                ),
                align="inline-end",
            ),
        ),
        input_group.root(
            input_group.addon(
                input_group.button(
                    hi("InformationCircleIcon"),
                    variant="secondary",
                    size="icon-xs",
                ),
                align="inline-start",
            ),
            input_group.addon(
                "https://",
                class_name="pl-1.5 text-muted-foreground",
                align="inline-start",
            ),
            input_group.input(id="input-secure-19"),
            input_group.addon(
                input_group.button(
                    hi(
                        "StarIcon",
                        class_name=rx.cond(
                            _input_star.value,
                            "text-blue-600 fill-blue-600",
                            "",
                        ),
                    ),
                    size="icon-xs",
                    on_click=_input_star.set_value(~_input_star.value),
                ),
                align="inline-end",
            ),
            class_name="[--radius:9999px]",
        ),
        input_group.root(
            input_group.input(placeholder="Type to search..."),
            input_group.addon(
                input_group.button(
                    "Search",
                    variant="secondary",
                ),
                align="inline-end",
            ),
        ),
        class_name="grid w-full max-w-sm gap-6",
    )
Saving...
Please wait...
import reflex as rx

from components.ui.input_group import input_group
from components.ui.spinner import spinner


def input_group_spinner() -> rx.Component:
    return rx.el.div(
        input_group.root(
            input_group.input(placeholder="Searching..."),
            input_group.addon(
                spinner(),
                align="inline-end",
            ),
        ),
        input_group.root(
            input_group.input(placeholder="Processing..."),
            input_group.addon(
                spinner(),
                align="inline-start",
            ),
        ),
        input_group.root(
            input_group.input(placeholder="Saving changes..."),
            input_group.addon(
                input_group.text("Saving..."),
                spinner(),
                align="inline-end",
            ),
        ),
        input_group.root(
            input_group.input(placeholder="Refreshing data..."),
            input_group.addon(
                spinner(),
                align="inline-start",
            ),
            input_group.addon(
                input_group.text(
                    "Please wait...",
                    class_name="text-muted-foreground",
                ),
                align="inline-end",
            ),
        ),
        class_name="grid w-full max-w-sm gap-4",
    )
import reflex as rx

from components.icons.hugeicon import hi
from components.ui.input_group import input_group
from components.ui.menu import menu


def input_group_dropdown() -> rx.Component:
    return rx.el.div(
        input_group.root(
            input_group.input(placeholder="Enter file name"),
            input_group.addon(
                menu.root(
                    menu.trigger(
                        render_=input_group.button(
                            hi("MoreHorizontal"),
                            variant="ghost",
                            aria_label="More",
                            size="icon-xs",
                        )
                    ),
                    menu.portal(
                        menu.positioner(
                            menu.popup(
                                menu.group(
                                    menu.item("Settings"),
                                    menu.item("Copy path"),
                                    menu.item("Open location"),
                                )
                            ),
                            align="end",
                        )
                    ),
                ),
                align="inline-end",
            ),
        ),
        input_group.root(
            input_group.input(placeholder="Enter search query"),
            input_group.addon(
                menu.root(
                    menu.trigger(
                        render_=input_group.button(
                            "Search In... ",
                            hi("ChevronDownIcon", class_name="size-3 ml-1 inline"),
                            variant="ghost",
                            class_name="!pr-1.5 text-xs",
                        )
                    ),
                    menu.portal(
                        menu.positioner(
                            menu.popup(
                                menu.group(
                                    menu.item("Documentation"),
                                    menu.item("Blog Posts"),
                                    menu.item("Changelog"),
                                )
                            ),
                            align="end",
                        )
                    ),
                ),
                align="inline-end",
            ),
        ),
        class_name="grid w-full max-w-sm gap-4",
    )

API Reference

input_group.root

The main component that wraps inputs and addons.

PropTypeDefault
class_namestring

input_group.addon

Displays icons, text, buttons, or other content alongside inputs.

For proper focus navigation, the input_group.addon component should be placed after the input. Set the align prop to position the addon.

PropTypeDefault
align"inline-start" | "inline-end" | "block-start" | "block-end""inline-start"
class_namestring

For input_group.input, use the inline-start or inline-end alignment. For input_group.textarea, use the block-start or block-end alignment.

The input_group.addon component can have multiple input_group.button components and icons.

input_group.button

Displays buttons within input groups. See the Button docs for more information.

PropTypeDefault
size"xs" | "icon-xs" | "sm" | "icon-sm""xs"
variant"default" | "destructive" | "outline" | "secondary" | "ghost" | "link""ghost"
class_namestring

input_group.input

Replacement for input() when building input groups. This component has the input group styles pre-applied and uses the unified data-slot="input-group-control" for focus state handling.

PropTypeDefault
class_namestring

All other props are passed through to the underlying Input() component.

input_group.textarea

Replacement for textarea() when building input groups. This component has the textarea group styles pre-applied and uses the unified data-slot="input-group-control" for focus state handling.

PropTypeDefault
class_namestring

All other props are passed through to the underlying textarea() component.