Skip to content
All Taste components

Forms

Input with icon & button

Inputs with a leading icon or a trailing action: search, copy a link, show a password.

search + shortcut · copy link · password reveal

/
"use client";
import { useState } from "react";
import { Check, Copy, Eye, EyeOff, Lock, Search } from "lucide-react";
import { cn } from "@/lib/utils";

const shell =
  "flex h-9 w-full min-w-0 items-center gap-2 rounded border border-border-strong bg-bg px-2.5 transition focus-within:border-primary focus-within:ring-[3px] focus-within:ring-primary/20";
const bare = "h-full min-w-0 flex-1 bg-transparent text-sm text-fg outline-none placeholder:text-fg-subtle";

export function SearchInput({ className, ...props }: React.InputHTMLAttributes<HTMLInputElement>) {
  return (
    <div className={cn(shell, className)}>
      <Search className="size-4 shrink-0 text-fg-subtle" aria-hidden />
      <input type="search" className={bare} {...props} />
      <kbd className="rounded-sm border border-b-2 border-border-strong bg-surface px-1.5 font-mono text-[11px] text-fg-muted">/</kbd>
    </div>
  );
}

export function CopyLinkInput({ url, label = "Link" }: { url: string; label?: string }) {
  const [copied, setCopied] = useState(false);

  async function copy() {
    await navigator.clipboard.writeText(url);
    setCopied(true);
    setTimeout(() => setCopied(false), 1600);
  }

  return (
    <div className="flex h-9 w-full min-w-0 overflow-hidden rounded border border-border-strong bg-bg transition focus-within:border-primary focus-within:ring-[3px] focus-within:ring-primary/20">
      <input readOnly value={url} aria-label={label} onFocus={(e) => e.target.select()} className={cn(bare, "px-3")} />
      <button
        type="button"
        onClick={copy}
        className="inline-flex shrink-0 items-center gap-1.5 border-l border-border bg-surface px-3 text-sm font-medium text-fg transition hover:bg-surface-2"
      >
        {copied ? <Check className="size-4 text-success" aria-hidden /> : <Copy className="size-4" aria-hidden />}
        {copied ? "Copied" : "Copy"}
      </button>
      <span className="sr-only" aria-live="polite">{copied ? "Link copied" : ""}</span>
    </div>
  );
}

export function PasswordInput(props: Omit<React.InputHTMLAttributes<HTMLInputElement>, "type">) {
  const [shown, setShown] = useState(false);
  return (
    <div className={shell}>
      <Lock className="size-4 shrink-0 text-fg-subtle" aria-hidden />
      <input type={shown ? "text" : "password"} className={bare} {...props} />
      <button
        type="button"
        onClick={() => setShown((s) => !s)}
        aria-label={shown ? "Hide password" : "Show password"}
        aria-pressed={shown}
        className="-mr-1 grid size-7 shrink-0 place-items-center rounded-sm text-fg-muted transition hover:bg-surface-2 hover:text-fg"
      >
        {shown ? <EyeOff className="size-4" aria-hidden /> : <Eye className="size-4" aria-hidden />}
      </button>
    </div>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.