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 --forceui add demo_input_groupui add input_group
Update 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
- 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>
}
}