Skip to content
ssntl/ui

Input group

A single-field form with the submit button inset, its corners concentric with the field.

Installation

$ pnpm dlx shadcn@latest add @ssntl/input-group

Usage

tsx
import { InputGroup, InputGroupButton, InputGroupInput } from "@/components/ui/input-group"

<InputGroup>
  <InputGroupInput aria-label="Search" placeholder="Orders, clients…" />
  <InputGroupButton aria-label="Search"><Search /></InputGroupButton>
</InputGroup>
  • Nested corners: the field is a pill with 4px padding around a 48px pill button, so both curves share one center.
  • For single-field forms only: search, newsletter, a prompt.
  • The icon-only button needs an aria-label; the input needs one too when there is no visible label.

Code

components/ui/input-group.tsx
import * as React from "react"

import { cn } from "@/lib/utils"
import { Button } from "@/registry/ssntl/ui/button"

// A single-field form (search, newsletter, prompt) with the submit button set
// inside the field. Nested corners: a pill with 4px padding around a 48px pill
// button, so both curves share one center (outer = inner + padding).
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="input-group"
      className={cn(
        "flex w-full min-w-0 items-center gap-1 rounded-full border border-input bg-card p-1 pl-5",
        "transition-[border-color] duration-(--duration-fast) ease-out hover:border-muted-foreground",
        "focus-within:border-foreground has-[input:focus-visible]:outline-2 has-[input:focus-visible]:outline-offset-2 has-[input:focus-visible]:outline-ring",
        "has-aria-invalid:border-destructive",
        className
      )}
      {...props}
    />
  )
}

function InputGroupInput({
  className,
  ...props
}: React.ComponentProps<"input">) {
  return (
    <input
      data-slot="input-group-input"
      className={cn(
        "h-12 min-w-0 flex-1 bg-transparent text-body text-foreground outline-none placeholder:text-muted-foreground",
        className
      )}
      {...props}
    />
  )
}

function InputGroupButton({
  size = "icon",
  className,
  ...props
}: React.ComponentProps<typeof Button>) {
  return (
    <Button
      data-slot="input-group-button"
      size={size}
      className={cn("shrink-0", className)}
      {...props}
    />
  )
}

export { InputGroup, InputGroupButton, InputGroupInput }