Input Group
A component that combines inputs with addons like icons, text, or buttons.
input
- Copy Demo
use icons::{Check, CreditCard, Info, Mail, Search, Star};
use leptos::prelude::*;
use crate::components::ui::input_group::{InputGroup, InputGroupAddon, InputGroupAddonAlign, InputGroupInput};
#[component]
pub fn DemoInputGroup() -> impl IntoView {
view! {
<div class="grid gap-6 w-full max-w-sm">
<InputGroup>
<InputGroupInput attr:placeholder="Search..." />
<InputGroupAddon>
<Search />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput attr:placeholder="Enter your email" />
<InputGroupAddon>
<Mail />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput attr:placeholder="Card number" />
<InputGroupAddon>
<CreditCard />
</InputGroupAddon>
<InputGroupAddon align=InputGroupAddonAlign::InlineEnd>
<Check />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput attr:placeholder="Card number" />
<InputGroupAddon align=InputGroupAddonAlign::InlineEnd>
<Star />
<Info />
</InputGroupAddon>
</InputGroup>
</div>
}
}
Installation
You can run either of the following commands:
# cargo install ui-cli --force
ui add demo_input_group
ui add input_groupUpdate the imports to match your project setup.
Copy and paste the following code into your project:
components/ui/input_group.rs
use leptos::prelude::*;
use leptos_ui::{clx, variants};
use tw_merge::{TwClass, TwVariant, tw_merge};
use crate::registry::ui::input::Input;
use crate::registry::ui::textarea::Textarea;
mod components {
use super::*;
clx! {InputGroupText, span, "text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4"}
}
pub use components::*;
/* ========================================================== */
/* ✨ FUNCTIONS ✨ */
/* ========================================================== */
#[component]
pub fn InputGroup(#[prop(into, optional)] class: String, children: Children) -> impl IntoView {
let merged_class = tw_merge!(
"group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none h-9 min-w-0 has-[>textarea]:h-auto has-[>[data-align=inline-start]]:[&>input]:pl-2 has-[>[data-align=inline-end]]:[&>input]:pr-2 has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
class
);
view! {
<div data-name="InputGroup" data-slot="input-group" role="group" class=merged_class>
{children()}
</div>
}
}
#[component]
pub fn InputGroupAddon(
#[prop(default = InputGroupAddonAlign::default())] align: InputGroupAddonAlign,
#[prop(into, optional)] class: String,
children: Children,
) -> impl IntoView {
let addon = InputGroupAddonClass { align };
let merged_class = addon.with_class(class);
let align_attr = match align {
InputGroupAddonAlign::InlineStart => "inline-start",
InputGroupAddonAlign::InlineEnd => "inline-end",
InputGroupAddonAlign::BlockStart => "block-start",
InputGroupAddonAlign::BlockEnd => "block-end",
};
view! {
<div
data-name="InputGroupAddon"
data-slot="input-group-addon"
data-align=align_attr
role="group"
class=merged_class
>
{children()}
</div>
}
}
#[derive(TwClass, Default)]
#[tw(
class = "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50"
)]
struct InputGroupAddonClass {
align: InputGroupAddonAlign,
}
#[derive(TwVariant)]
pub enum InputGroupAddonAlign {
#[tw(default, class = "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]")]
InlineStart,
#[tw(class = "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]")]
InlineEnd,
#[tw(class = "order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5")]
BlockStart,
#[tw(class = "order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5")]
BlockEnd,
}
variants! {
InputGroupButton {
base: "text-sm shadow-none flex gap-2 items-center",
variants: {
variant: {
Ghost: "",
},
size: {
Xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
Sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
IconXs: "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
IconSm: "size-8 p-0 has-[>svg]:p-0",
}
},
component: {
element: button
}
}
}
#[component]
pub fn InputGroupInput(#[prop(into, optional)] class: String) -> impl IntoView {
let merged_class = tw_merge!(
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
class
);
view! { <Input attr:data-slot="input-group-control" class=merged_class /> }
}
#[component]
pub fn InputGroupTextarea(#[prop(into, optional)] class: String) -> impl IntoView {
let merged_class = tw_merge!(
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
class
);
view! { <Textarea class=merged_class attr:data-slot="input-group-control" /> }
}
Update the imports to match your project setup.
Usage
use icons::Search;
use crate::components::ui::input_group::{InputGroup, InputGroupAddon, InputGroupInput};<InputGroup>
<InputGroupInput attr:placeholder="Search..." />
<InputGroupAddon>
<Search />
</InputGroupAddon>
</InputGroup>Examples
Input Group Text
Input field combined with text addons for labels, units, or prefixes. This example demonstrates how to build enhanced Input components in Leptos using InputGroupAddon for displaying contextual text and improving form usability in Rust applications.
- Copy Demo
$
USD
https://
.com
@company.com
use leptos::prelude::*;
use crate::components::ui::input_group::{
InputGroup, InputGroupAddon, InputGroupAddonAlign, InputGroupInput, InputGroupText,
};
#[component]
pub fn DemoInputGroupText() -> impl IntoView {
view! {
<div class="grid gap-6 w-full max-w-sm">
<InputGroup>
<InputGroupAddon>
<InputGroupText>$</InputGroupText>
</InputGroupAddon>
<InputGroupInput attr:placeholder="0.00" />
<InputGroupAddon align=InputGroupAddonAlign::InlineEnd>
<InputGroupText>USD</InputGroupText>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupAddon>
<InputGroupText>{"https://"}</InputGroupText>
</InputGroupAddon>
<InputGroupInput attr:placeholder="example.com" class="!pl-0.5" />
<InputGroupAddon align=InputGroupAddonAlign::InlineEnd>
<InputGroupText>{".com"}</InputGroupText>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput attr:placeholder="Enter your username" />
<InputGroupAddon align=InputGroupAddonAlign::InlineEnd>
<InputGroupText>@company.com</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
}
}