Input Group

An input with icons, text and buttons attached on either side.

⌘K
import { Kbd } from '@/components/ui/kbd'
import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group'

export default function InputGroupDemo() {
  return (
    <InputGroup className="max-w-sm">
      <InputGroupAddon>
        <SearchIcon />
      </InputGroupAddon>
      <InputGroupInput type="search" placeholder="Search components…" aria-label="Search" />
      <InputGroupAddon align="inline-end">
        <Kbd>⌘K</Kbd>
      </InputGroupAddon>
    </InputGroup>
  )
}

function SearchIcon() {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
      <circle cx="11" cy="11" r="8" />
      <path d="m21 21-4.3-4.3" />
    </svg>
  )
}

Installation

pnpm dlx shadcn@latest add https://kagadmodyaa.vercel.app/r/input-group.json

Needs a React + Tailwind project set up with shadcn. Copies the component source straight into your project, so you can edit it.

Input Group imports cn() from lib/utils.ts — add it too if you don't have it yet:

import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Usage

import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText, InputGroupButton } from '@/components/ui/input-group'

<InputGroup>
  <InputGroupAddon>
    <InputGroupText>https://</InputGroupText>
  </InputGroupAddon>
  <InputGroupInput placeholder="example.com" />
  <InputGroupAddon align="inline-end">
    <InputGroupButton>Go</InputGroupButton>
  </InputGroupAddon>
</InputGroup>

Examples

Text addons and a copy button

https://
.kagadmodyaa.com
'use client'

import * as React from 'react'
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText } from '@/components/ui/input-group'

/** Text addons on both sides, and a button that copies the value. */
export default function InputGroupAddons() {
  const [slug, setSlug] = React.useState('my-studio')
  const [copied, setCopied] = React.useState(false)
  const url = `https://${slug}.kagadmodyaa.com`

  async function copy() {
    try {
      await navigator.clipboard.writeText(url)
    } catch {
      // Clipboard blocked: still give feedback.
    }
    setCopied(true)
    setTimeout(() => setCopied(false), 1500)
  }

  return (
    <div className="grid w-full max-w-sm gap-4">
      <InputGroup>
        <InputGroupAddon>
          <InputGroupText>https://</InputGroupText>
        </InputGroupAddon>
        <InputGroupInput value={slug} onChange={event => setSlug(event.target.value)} aria-label="Subdomain" />
        <InputGroupAddon align="inline-end">
          <InputGroupText>.kagadmodyaa.com</InputGroupText>
        </InputGroupAddon>
      </InputGroup>

      <InputGroup>
        <InputGroupInput readOnly value={url} aria-label="Share link" />
        <InputGroupAddon align="inline-end">
          <InputGroupButton aria-label="Copy link" onClick={copy}>
            {copied ? <CheckIcon /> : <CopyIcon />}
            {copied ? 'Copied' : 'Copy'}
          </InputGroupButton>
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}

function CopyIcon() {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
      <rect width="14" height="14" x="8" y="8" rx="2" />
      <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
    </svg>
  )
}

function CheckIcon() {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
      <path d="M20 6 9 17l-5-5" />
    </svg>
  )
}

API Reference

NameTypeDefaultDescription
InputGroupdiv props—The bordered wrapper. It shows the focus ring when the input inside is focused, and the invalid look when the input has aria-invalid.
InputGroupAddon align'inline-start' | 'inline-end''inline-start'Which side of the input the addon sits on. Clicking an addon focuses the input.
InputGroupInputinput props—The borderless input. Use it instead of Input inside a group.
InputGroupTextspan props—Plain text in an addon — a URL prefix, a unit, a currency sign.
InputGroupButtonButton propsvariant 'ghost', size 'sm'A compact button that fits inside the group.