Rendered from the shared Frontfriend stories with React Base, the same
preview Studio shows. Styling comes from your Design System; the code below
is native to Reka UI.
Installation
Complete project setup first. Your configuration
must select vue/reka and include your Design System ID.
pnpm exec frontfriend download input-group -f vue --primitive reka
Review dependencies reported by the downloader and keep your source
changes in version control.
Usage
<script setup lang="ts">
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
"align": "inline-start",
"placeholder": "Search...",
"disabled": false,
"text": "Search",
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown> & {
align?: "inline-start" | "inline-end" | "block-start" | "block-end";
};
</script>
<template>
<InputGroup
><InputGroupInput
:placeholder="args.placeholder"
:disabled="args.disabled"
/><InputGroupAddon :align="args.align">{{
args.text
}}</InputGroupAddon></InputGroup
>
</template>
Examples
Demo
<script setup lang="ts">
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { SearchIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<InputGroup class="w-[320px] max-w-xs">
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</InputGroup>
</template>
Inline Start
<script setup lang="ts">
import { Field } from "@/components/ui/field";
import { FieldLabel } from "@/components/ui/field";
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { SearchIcon } from "lucide-vue-next";
import { FieldDescription } from "@/components/ui/field";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<Field class="w-[360px] max-w-sm flex-col">
<FieldLabel for="inline-start-input">Input</FieldLabel>
<InputGroup>
<InputGroupInput id="inline-start-input" placeholder="Search..." />
<InputGroupAddon align="inline-start">
<SearchIcon />
</InputGroupAddon>
</InputGroup>
<FieldDescription>Icon positioned at the start.</FieldDescription>
</Field>
</template>
Inline End
<script setup lang="ts">
import { Field } from "@/components/ui/field";
import { FieldLabel } from "@/components/ui/field";
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { EyeOffIcon } from "lucide-vue-next";
import { FieldDescription } from "@/components/ui/field";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<Field class="w-[360px] max-w-sm flex-col">
<FieldLabel for="inline-end-input">Input</FieldLabel>
<InputGroup>
<InputGroupInput
id="inline-end-input"
type="password"
placeholder="Enter password"
/>
<InputGroupAddon align="inline-end">
<EyeOffIcon />
</InputGroupAddon>
</InputGroup>
<FieldDescription>Icon positioned at the end.</FieldDescription>
</Field>
</template>
Block Start
<script setup lang="ts">
import { FieldGroup } from "@/components/ui/field";
import { Field } from "@/components/ui/field";
import { FieldLabel } from "@/components/ui/field";
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { InputGroupText } from "@/components/ui/input-group";
import { FieldDescription } from "@/components/ui/field";
import { InputGroupTextarea } from "@/components/ui/input-group";
import { FileCodeIcon } from "lucide-vue-next";
import { InputGroupButton } from "@/components/ui/input-group";
import { CopyIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<FieldGroup class="w-[360px] max-w-sm">
<Field class="w-full flex-col">
<FieldLabel for="block-start-input">Input</FieldLabel>
<InputGroup class="h-auto">
<InputGroupInput id="block-start-input" placeholder="Enter your name" />
<InputGroupAddon align="block-start"
><InputGroupText>Full Name</InputGroupText></InputGroupAddon
>
</InputGroup>
<FieldDescription>Header positioned above the input.</FieldDescription>
</Field>
<Field class="w-full flex-col">
<FieldLabel for="block-start-textarea">Textarea</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="block-start-textarea"
placeholder="console.log('Hello, world!');"
class="font-mono text-sm"
/>
<InputGroupAddon align="block-start" class="border-b">
<FileCodeIcon />
<InputGroupText class="font-mono">script.js</InputGroupText>
<InputGroupButton size="icon-xs" class="ml-auto"
><CopyIcon /><span class="sr-only">Copy</span></InputGroupButton
>
</InputGroupAddon>
</InputGroup>
<FieldDescription>Header positioned above the textarea.</FieldDescription>
</Field>
</FieldGroup>
</template>
Block End
<script setup lang="ts">
import { FieldGroup } from "@/components/ui/field";
import { Field } from "@/components/ui/field";
import { FieldLabel } from "@/components/ui/field";
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { InputGroupText } from "@/components/ui/input-group";
import { FieldDescription } from "@/components/ui/field";
import { InputGroupTextarea } from "@/components/ui/input-group";
import { InputGroupButton } from "@/components/ui/input-group";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<FieldGroup class="w-[360px] max-w-sm">
<Field class="w-full flex-col">
<FieldLabel for="block-end-input">Input</FieldLabel>
<InputGroup class="h-auto">
<InputGroupInput id="block-end-input" placeholder="Enter amount" />
<InputGroupAddon align="block-end"
><InputGroupText>USD</InputGroupText></InputGroupAddon
>
</InputGroup>
<FieldDescription>Footer positioned below the input.</FieldDescription>
</Field>
<Field class="w-full flex-col">
<FieldLabel for="block-end-textarea">Textarea</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="block-end-textarea"
placeholder="Write a comment..."
/>
<InputGroupAddon align="block-end" class="border-t">
<InputGroupText>0/280</InputGroupText>
<InputGroupButton variant="default" size="sm" class="ml-auto"
>Post</InputGroupButton
>
</InputGroupAddon>
</InputGroup>
<FieldDescription>Footer positioned below the textarea.</FieldDescription>
</Field>
</FieldGroup>
</template>
Icon
<script setup lang="ts">
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { SearchIcon } from "lucide-vue-next";
import { MailIcon } from "lucide-vue-next";
import { CreditCardIcon } from "lucide-vue-next";
import { CheckIcon } from "lucide-vue-next";
import { StarIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div class="grid w-[360px] max-w-sm gap-6">
<InputGroup
><InputGroupInput placeholder="Search..." /><InputGroupAddon
><SearchIcon /></InputGroupAddon
></InputGroup>
<InputGroup
><InputGroupInput
type="email"
placeholder="Enter your email" /><InputGroupAddon
><MailIcon /></InputGroupAddon
></InputGroup>
<InputGroup
><InputGroupInput placeholder="Card number" /><InputGroupAddon
><CreditCardIcon /></InputGroupAddon
><InputGroupAddon align="inline-end"><CheckIcon /></InputGroupAddon
></InputGroup>
<InputGroup
><InputGroupInput placeholder="Card number" /><InputGroupAddon
align="inline-end"
><StarIcon /><SearchIcon /></InputGroupAddon
></InputGroup>
</div>
</template>
Text
<script setup lang="ts">
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { InputGroupText } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupTextarea } from "@/components/ui/input-group";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div class="grid w-[360px] max-w-sm gap-6">
<InputGroup
><InputGroupAddon><InputGroupText>$</InputGroupText></InputGroupAddon
><InputGroupInput placeholder="0.00" /><InputGroupAddon align="inline-end"
><InputGroupText>USD</InputGroupText></InputGroupAddon
></InputGroup
>
<InputGroup
><InputGroupAddon
><InputGroupText>https://</InputGroupText></InputGroupAddon
><InputGroupInput placeholder="example.com" /><InputGroupAddon
align="inline-end"
><InputGroupText>.com</InputGroupText></InputGroupAddon
></InputGroup
>
<InputGroup
><InputGroupInput placeholder="Enter your username" /><InputGroupAddon
align="inline-end"
><InputGroupText>@company.com</InputGroupText></InputGroupAddon
></InputGroup
>
<InputGroup
><InputGroupTextarea placeholder="Enter your message" /><InputGroupAddon
align="block-end"
><InputGroupText class="text-xs"
>120 characters left</InputGroupText
></InputGroupAddon
></InputGroup
>
</div>
</template>
<script setup lang="ts">
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { InputGroupButton } from "@/components/ui/input-group";
import { CopyIcon } from "lucide-vue-next";
import { InputGroupText } from "@/components/ui/input-group";
import { StarIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div class="grid w-[360px] max-w-sm gap-6">
<InputGroup>
<InputGroupInput placeholder="https://x.com/shadcn" readonly />
<InputGroupAddon align="inline-end">
<InputGroupButton aria-label="Copy" title="Copy" size="icon-xs">
<CopyIcon />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup
><InputGroupAddon
><InputGroupText>https://</InputGroupText></InputGroupAddon
><InputGroupInput placeholder="frontfriend.dev" /><InputGroupAddon
align="inline-end"
><InputGroupButton size="icon-xs"
><StarIcon /></InputGroupButton></InputGroupAddon
></InputGroup>
<InputGroup
><InputGroupInput placeholder="Type to search..." /><InputGroupAddon
align="inline-end"
><InputGroupButton variant="secondary"
>Search</InputGroupButton
></InputGroupAddon
></InputGroup
>
</div>
</template>
Kbd Example
<script setup lang="ts">
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { SearchIcon } from "lucide-vue-next";
import { Kbd } from "@/components/ui/kbd";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<InputGroup class="w-[360px] max-w-sm">
<InputGroupInput placeholder="Search..." />
<InputGroupAddon><SearchIcon /></InputGroupAddon>
<InputGroupAddon align="inline-end"><Kbd>⌘K</Kbd></InputGroupAddon>
</InputGroup>
</template>
Dropdown
<script setup lang="ts">
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { DropdownMenu } from "@/components/ui/dropdown-menu";
import { DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { InputGroupButton } from "@/components/ui/input-group";
import { MoreHorizontalIcon } from "lucide-vue-next";
import { DropdownMenuContent } from "@/components/ui/dropdown-menu";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { ChevronDownIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div class="grid w-[360px] max-w-sm gap-4">
<InputGroup>
<InputGroupInput placeholder="Enter file name" />
<InputGroupAddon align="inline-end">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<InputGroupButton variant="ghost" aria-label="More" size="icon-xs"
><MoreHorizontalIcon
/></InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuItem>Copy path</DropdownMenuItem>
<DropdownMenuItem>Open location</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
<InputGroup
><InputGroupInput placeholder="Enter search query" /><InputGroupAddon
align="inline-end"
><InputGroupButton class="pr-1.5 text-xs"
>Search In...
<ChevronDownIcon class="size-3" /></InputGroupButton></InputGroupAddon
></InputGroup>
</div>
</template>
Spinner Example
<script setup lang="ts">
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { Spinner } from "@/components/ui/spinner";
import { InputGroupText } from "@/components/ui/input-group";
import { LoaderIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div class="grid w-[360px] max-w-sm gap-4">
<InputGroup
><InputGroupInput placeholder="Searching..." /><InputGroupAddon
align="inline-end"
><Spinner /></InputGroupAddon
></InputGroup>
<InputGroup
><InputGroupInput placeholder="Processing..." /><InputGroupAddon
><Spinner /></InputGroupAddon
></InputGroup>
<InputGroup
><InputGroupInput placeholder="Saving changes..." /><InputGroupAddon
align="inline-end"
><InputGroupText>Saving...</InputGroupText><Spinner /></InputGroupAddon
></InputGroup>
<InputGroup
><InputGroupInput placeholder="Refreshing data..." /><InputGroupAddon
><LoaderIcon class="animate-spin" /></InputGroupAddon
><InputGroupAddon align="inline-end"
><InputGroupText>Please wait...</InputGroupText></InputGroupAddon
></InputGroup
>
</div>
</template>
Textarea
<script setup lang="ts">
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupTextarea } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { InputGroupText } from "@/components/ui/input-group";
import { InputGroupButton } from "@/components/ui/input-group";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div class="grid w-[448px] max-w-md gap-4">
<InputGroup>
<InputGroupTextarea
placeholder="console.log('Hello, world!');"
class="min-h-[200px]"
/>
<InputGroupAddon align="block-end" class="border-t">
<InputGroupText>Line 1, Column 1</InputGroupText>
<InputGroupButton size="sm" class="ml-auto" variant="default"
>Run</InputGroupButton
>
</InputGroupAddon>
</InputGroup>
</div>
</template>
<script setup lang="ts">
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { InputGroupButton } from "@/components/ui/input-group";
import * as UI from "@/components/ui/input-group";
const Component = UI.InputGroup;
const args = {
} satisfies Partial<
InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div class="grid w-[360px] max-w-sm gap-6">
<InputGroup>
<textarea
data-slot="input-group-control"
class="flex field-sizing-content min-h-16 w-full resize-none rounded-md bg-transparent px-3 py-2.5 text-base transition-[color,box-shadow] outline-none md:text-sm"
placeholder="Autoresize textarea..."
/>
<InputGroupAddon align="block-end">
<InputGroupButton class="ml-auto" size="sm" variant="default"
>Submit</InputGroupButton
>
</InputGroupAddon>
</InputGroup>
</div>
</template>
API reference
Frontfriend-owned props. Native HTML and inherited primitive props
remain documented by Reka UI.
InputGroup
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
InputGroupAddon
| Prop | Type | Default | Description |
align | InputGroupVariants["align"] | — | |
class | HTMLAttributes["class"] | — | |
InputGroupButton
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
size | InputGroupButtonVariants["size"] | — | |
variant | ButtonVariants["variant"] | — | |
InputGroupInput
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
InputGroupText
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
InputGroupTextarea
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
Configuration determines the actual classes and available styling hooks.
See Theming & component configuration before introducing
a repeated local override.