- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Avatar
- Badge
- Breadcrumb
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Data Table
- Date Picker
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Form
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Menubar
- Native Select
- Navigation Menu
- Pagination
- Pin Input
- Popover
- Progress
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Stepper
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
- Typography
<script setup lang="ts">
import { ArrowUpIcon, CheckIcon, InfoIcon, PlusIcon, Search } from 'lucide-vue-next'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea } from '@/components/ui/input-group'
import { Separator } from '@/components/ui/separator'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
</script>
<template>
<div class="grid w-full max-w-sm gap-6">
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
12 results
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="example.com" class="!pl-1" />
<InputGroupAddon>
<InputGroupText>https://</InputGroupText>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger as-child>
<InputGroupButton class="rounded-full" size="icon-xs">
<InfoIcon class="size-4" />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>This is content in a tooltip.</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupTextarea placeholder="Ask, Search or Chat..." />
<InputGroupAddon align="block-end">
<InputGroupButton
variant="outline"
class="rounded-full"
size="icon-xs"
>
<PlusIcon class="size-4" />
</InputGroupButton>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<InputGroupButton variant="ghost">
Auto
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
class="[--radius:0.95rem]"
>
<DropdownMenuItem>Auto</DropdownMenuItem>
<DropdownMenuItem>Agent</DropdownMenuItem>
<DropdownMenuItem>Manual</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupText class="ml-auto">
52% used
</InputGroupText>
<Separator orientation="vertical" class="!h-4" />
<InputGroupButton
variant="default"
class="rounded-full"
size="icon-xs"
disabled
>
<ArrowUpIcon class="size-4" />
<span class="sr-only">Send</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="@shadcn" />
<InputGroupAddon align="inline-end">
<div class="flex items-center justify-center rounded-full bg-primary text-primary-foreground size-4">
<CheckIcon class="size-3" />
</div>
</InputGroupAddon>
</InputGroup>
</div>
</template>Installation
pnpm dlx shadcn-vue@latest add input-group
Usage
<script setup lang="ts">
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from '@/components/ui/input-group'
</script>
<template>
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupButton>Search</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</template>Examples
Icon
<script setup lang="ts">
import { CheckIcon, CreditCardIcon, InfoIcon, MailIcon, SearchIcon, StarIcon } from 'lucide-vue-next'
import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group'
</script>
<template>
<div class="grid w-full 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 />
<InfoIcon />
</InputGroupAddon>
</InputGroup>
</div>
</template>Text
Display additional text information alongside inputs.
<script setup lang="ts">
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText, InputGroupTextarea } from '@/components/ui/input-group'
</script>
<template>
<div class="grid w-full 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" class="!pl-0.5" />
<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 text-muted-foreground">
120 characters left
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
</template>Button
Add buttons to perform actions within the input group.
<script setup lang="ts">
import { useClipboard } from '@vueuse/core'
import { CheckIcon, CopyIcon, InfoIcon, StarIcon } from 'lucide-vue-next'
import { ref } from 'vue'
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/ui/input-group'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
const isFavorite = ref(false)
const source = ref('hello')
const { text, copy, copied, isSupported } = useClipboard({ source })
</script>
<template>
<div class="grid w-full max-w-sm gap-6">
<InputGroup>
<InputGroupInput placeholder="https://x.com/shadcn" read-only />
<InputGroupAddon align="inline-end">
<InputGroupButton
aria-label="Copy"
title="Copy"
size="icon-xs"
@click="copy('https://x.com/shadcn')"
>
<CheckIcon v-if="!copied" />
<CopyIcon v-if="copied" />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup class="[--radius:9999px]">
<Popover>
<PopoverTrigger as-child>
<InputGroupAddon>
<InputGroupButton variant="secondary" size="icon-xs">
<InfoIcon />
</InputGroupButton>
</InputGroupAddon>
</PopoverTrigger>
<PopoverContent
align="start"
class="flex flex-col gap-1 text-sm rounded-xl"
>
<p class="font-medium">
Your connection is not secure.
</p>
<p>You should not enter any sensitive information on this site.</p>
</PopoverContent>
</Popover>
<InputGroupAddon class="text-muted-foreground pl-1.5">
https://
</InputGroupAddon>
<InputGroupInput id="input-secure-19" />
<InputGroupAddon align="inline-end">
<InputGroupButton
size="icon-xs"
@click="isFavorite = !isFavorite"
>
<StarIcon
data-favorite="{isFavorite}"
class="data-[favorite=true]:fill-blue-600 data-[favorite=true]:stroke-blue-600"
/>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Type to search..." />
<InputGroupAddon align="inline-end">
<InputGroupButton variant="secondary">
Search
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
</template>Tooltip
Add tooltips to provide additional context or help.
<script setup lang="ts">
import { HelpCircle, InfoIcon } from 'lucide-vue-next'
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/ui/input-group'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
</script>
<template>
<div class="grid w-full max-w-sm gap-4">
<InputGroup>
<InputGroupInput placeholder="Enter password" type="password" />
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger as-child>
<InputGroupButton
variant="ghost"
aria-label="Info"
size="icon-xs"
>
<InfoIcon />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>
<p>Password must be at least 8 characters</p>
</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Your email address" />
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger as-child>
<InputGroupButton
variant="ghost"
aria-label="Help"
size="icon-xs"
>
<HelpCircle />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>
<p>We'll use this to send you notifications</p>
</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Enter API key" />
<Tooltip>
<TooltipTrigger as-child>
<InputGroupAddon>
<InputGroupButton
variant="ghost"
aria-label="Help"
size="icon-xs"
>
<HelpCircle />
</InputGroupButton>
</InputGroupAddon>
</TooltipTrigger>
<TooltipContent side="left">
<p>Click for help with API keys</p>
</TooltipContent>
</Tooltip>
</InputGroup>
</div>
</template>Textarea
Input groups also work with textarea components. Use block-start or block-end for alignment.
<script setup lang="ts">
import { BracesIcon, CopyIcon, CornerDownLeftIcon, RefreshCwIcon } from 'lucide-vue-next'
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupTextarea } from '@/components/ui/input-group'
</script>
<template>
<div class="grid w-full max-w-md gap-4">
<InputGroup>
<InputGroupTextarea
id="textarea-code-32"
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 <CornerDownLeftIcon />
</InputGroupButton>
</InputGroupAddon>
<InputGroupAddon align="block-start" class="border-b">
<InputGroupText class="font-mono font-medium">
<BracesIcon />
script.js
</InputGroupText>
<InputGroupButton class="ml-auto" size="icon-xs">
<RefreshCwIcon />
</InputGroupButton>
<InputGroupButton variant="ghost" size="icon-xs">
<CopyIcon />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
</template>Spinner
Show loading indicators while processing input.
<script setup lang="ts">
import { LoaderIcon } from 'lucide-vue-next'
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from '@/components/ui/input-group'
import { Spinner } from '@/components/ui/spinner'
</script>
<template>
<div class="grid w-full max-w-sm gap-4">
<InputGroup data-disabled>
<InputGroupInput placeholder="Searching..." disabled />
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled>
<InputGroupInput placeholder="Processing..." disabled />
<InputGroupAddon>
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled>
<InputGroupInput placeholder="Saving changes..." disabled />
<InputGroupAddon align="inline-end">
<InputGroupText>Saving...</InputGroupText>
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled>
<InputGroupInput placeholder="Refreshing data..." disabled />
<InputGroupAddon>
<LoaderIcon class="animate-spin" />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupText class="text-muted-foreground">
Please wait...
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
</template>Label
Add labels within input groups to improve accessibility.
<script setup lang="ts">
import { InfoIcon } from 'lucide-vue-next'
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/ui/input-group'
import { Label } from '@/components/ui/label'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
</script>
<template>
<div class="grid w-full max-w-sm gap-4">
<InputGroup>
<InputGroupInput id="email" placeholder="shadcn" />
<InputGroupAddon>
<Label for="email">@</Label>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="email-2" placeholder="shadcn@vercel.com" />
<InputGroupAddon align="block-start">
<Label for="email-2" class="text-foreground">
Email
</Label>
<Tooltip>
<TooltipTrigger as-child>
<InputGroupButton
variant="ghost"
aria-label="Help"
class="ml-auto rounded-full"
size="icon-xs"
>
<InfoIcon />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>
<p>We'll use this to send you notifications</p>
</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
</div>
</template>Dropdown
Pair input groups with dropdown menus for complex interactions.
<script setup lang="ts">
import { ChevronDownIcon, MoreHorizontal } from 'lucide-vue-next'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/ui/input-group'
</script>
<template>
<div class="grid w-full 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"
>
<MoreHorizontal />
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuItem>Copy path</DropdownMenuItem>
<DropdownMenuItem>Open location</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
<InputGroup class="[--radius:1rem]">
<InputGroupInput placeholder="Enter search query" />
<InputGroupAddon align="inline-end">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<InputGroupButton variant="ghost" class="!pr-1.5 text-xs">
Search In... <ChevronDownIcon class="size-3" />
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="[--radius:0.95rem]">
<DropdownMenuItem>Documentation</DropdownMenuItem>
<DropdownMenuItem>Blog Posts</DropdownMenuItem>
<DropdownMenuItem>Changelog</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
</div>
</template>Button Group
Wrap input groups with button groups to create prefixes and suffixes.
<script setup lang="ts">
import { Link2Icon } from 'lucide-vue-next'
import { ButtonGroup, ButtonGroupText } from '@/components/ui/button-group'
import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group'
import { Label } from '@/components/ui/label'
</script>
<template>
<div class="grid w-full max-w-sm">
<ButtonGroup class="!gap-0">
<ButtonGroupText as-child>
<Label for="url">https://</Label>
</ButtonGroupText>
<InputGroup>
<InputGroupInput id="url" />
<InputGroupAddon align="inline-end">
<Link2Icon />
</InputGroupAddon>
</InputGroup>
<ButtonGroupText>.com</ButtonGroupText>
</ButtonGroup>
</div>
</template>Custom Input
Add the data-slot="input-group-control" attribute to your custom input for automatic behavior and focus state handling.
No style is applied to the custom input. Apply your own styles using the class prop.
<script setup lang="ts">
import { InputGroup, InputGroupAddon, InputGroupButton } from '@/components/ui/input-group'
</script>
<template>
<div class="grid w-full 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><script setup lang="ts">
import { InputGroup, InputGroupAddon, InputGroupButton } from '@/components/ui/input-group'
</script>
<template>
<div class="grid w-full 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
InputGroup
The main component that wraps inputs and addons.
| Prop | Type | Default |
|---|---|---|
class | string |
<InputGroup>
<InputGroupInput />
<InputGroupAddon />
</InputGroup>InputGroupAddon
Displays icons, text, buttons, or other content alongside inputs.
For proper focus navigation, the InputGroupAddon component should be placed
after the input. Set the align prop to position the addon.
| Prop | Type | Default |
|---|---|---|
align | "inline-start" | "inline-end" | "block-start" | "block-end" | "inline-start" |
class | string |
<InputGroupAddon align="inline-end">
<SearchIcon />
</InputGroupAddon>For <InputGroupInput />, use the inline-start or inline-end alignment. For <InputGroupTextarea />, use the block-start or block-end alignment.
The InputGroupAddon component can have multiple InputGroupButton components and icons.
<InputGroupAddon>
<InputGroupButton>Button</InputGroupButton>
<InputGroupButton>Button</InputGroupButton>
</InputGroupAddon>InputGroupButton
Displays buttons within input groups.
| Prop | Type | Default |
|---|---|---|
size | "xs" | "icon-xs" | "sm" | "icon-sm" | "xs" |
variant | "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "ghost" |
class | string |
<InputGroupButton>
Button
</InputGroupButton>
<InputGroupButton size="icon-xs" aria-label="Copy">
<CopyIcon />
</InputGroupButton>InputGroupInput
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.
| Prop | Type | Default |
|---|---|---|
class | string |
All other props are passed through to the underlying <Input /> component.
<InputGroup>
<InputGroupInput placeholder="Enter text..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>InputGroupTextarea
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.
| Prop | Type | Default |
|---|---|---|
class | string |
All other props are passed through to the underlying <Textarea /> component.
<InputGroup>
<InputGroupTextarea placeholder="Enter message..." />
<InputGroupAddon align="block-end">
<InputGroupButton>Send</InputGroupButton>
</InputGroupAddon>
</InputGroup>