Meet Frontfriend.Your brand. Your rules. Your agents, connected.Explore what’s new
DocumentationInputgroup

Inputgroup

Interactive preview

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>

Button

<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>

Custom Input

<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.