Design system
GitHub

Component

Input

Single-line text field with standard and search variants.

npx -y github:anwarlemu/agentcard-brand-cli#release add input
input.preview

Preview
input.search
  • Create a card
  • Set spending limits
  • Manage merchants
Search
input.states

Enter a valid work email address.

Set by your team admin.

States

Usage

use-it-for.md

Use inside Field for a label and validation message. Set an appropriate input type. Use variant="search" for search or filtering and connect changes to the results.

never.md
  • Rounded form fields (only the search box has corners)
  • Gray filled inputs
  • Placeholder text as the only label
  • A glow or ring on focus (the brand draws the outline)

Dependencies: field, class-variance-authority.

Example

examples/input.tsx
"use client";

import { useState } from "react";
import { Field } from "@/components/ui/field";
import { Input } from "@/components/ui/input";

export function Site() {
  return (
    <div className="grid max-w-170 gap-4">
      <Field label="Your name" htmlFor="input-name">
        <Input id="input-name" autoComplete="name" />
      </Field>
      <Field label="Work email" htmlFor="input-email">
        <Input id="input-email" type="email" autoComplete="email" />
      </Field>
      <div className="grid gap-4 md:grid-cols-2">
        <Field label="Company" htmlFor="input-company">
          <Input id="input-company" />
        </Field>
        <Field label="Website" htmlFor="input-site">
          <Input id="input-site" placeholder="acme.com" />
        </Field>
      </div>
    </div>
  );
}

export function States() {
  const [email, setEmail] = useState("ada@");
  const [valid, setValid] = useState(false);
  return (
    <div className="grid max-w-170 gap-4">
      <Field label="Work email" htmlFor="input-invalid" error={valid ? undefined : "Enter a valid work email address."} help="Email address accepted.">
        <Input id="input-invalid" type="email" value={email} required onChange={(event) => { setEmail(event.target.value); setValid(event.target.validity.valid); }} />
      </Field>
      <Field label="Card nickname" htmlFor="input-disabled" help="Set by your team admin.">
        <Input id="input-disabled" defaultValue="Travel agent" disabled />
      </Field>
    </div>
  );
}

export function Search() {
  const [query, setQuery] = useState("");
  const guides = ["Create a card", "Set spending limits", "Manage merchants"].filter((guide) => guide.toLowerCase().includes(query.toLowerCase().trim()));
  return (
    <div className="max-w-80">
      <Input variant="search" placeholder="Search the guides" aria-label="Search the guides" value={query} onChange={(event) => setQuery(event.target.value)} />
      <ul aria-live="polite" className="mt-3 text-body-sm text-fg-muted">{guides.length ? guides.map((guide) => <li key={guide}>{guide}</li>) : <li>No guides match your search.</li>}</ul>
    </div>
  );
}

Source

components/ui/input.tsx
"use client";

import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";

import { useFieldControl } from "@/components/ui/field";
import { cn } from "@/lib/utils";

// Two fields. `field` is the get-started form's (agentcard.sh/get-started, .v7-gate-field input, computed): square, a
// 35 percent ink border, 18px Inter at 1.3 leading, 10px by 12px of padding, at least 44px tall; focus draws a 2px blue
// outline just inside the edge, at once (no transition); an invalid field's border is red. `search` is the guides'
// search box (agentcard-website src/app/globals.css .v7-guide-search): 36px, 8px corners, a 12 percent ink edge that
// darkens to 24 percent under the pointer (160ms ease-out), white with a faint inner shadow, 14px Inter, a 15px ink
// search mark 12px in with 10px to the text; focus is the 2px blue outline 2px out.
const inputVariants = cva(
  "flex w-full min-w-0 bg-surface text-fg outline-none disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        field:
          "min-h-11 rounded-none border border-line-strong px-3 py-2.5 font-sans text-h5 leading-snug placeholder:text-fg-faint focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-solid focus-visible:outline-focus-ring aria-invalid:border-danger file:border-0 file:bg-transparent file:font-display file:text-body-sm file:text-fg",
        search:
          "h-9 rounded-control border border-line-field pr-3 pl-9.25 font-sans text-body-sm shadow-inset transition-[border-color] duration-160 ease-out placeholder:text-fg-muted hover:border-line-field-hover focus-visible:outline-2 focus-visible:outline-solid motion-reduce:transition-none focus-visible:outline-offset-2 focus-visible:outline-focus-ring [&::-webkit-search-cancel-button]:hidden",
      },
    },
    defaultVariants: { variant: "field" },
  },
);

function SearchIcon() {
  return (
    <svg aria-hidden viewBox="0 0 16 16" className="pointer-events-none absolute top-1/2 left-3 size-3.75 -translate-y-1/2 fill-none stroke-current text-fg [stroke-linecap:round] [stroke-width:1.5]">
      <circle cx="7" cy="7" r="4.75" />
      <path d="M10.5 10.5 14 14" />
    </svg>
  );
}

function Input({ className, type, variant, ...props }: React.ComponentProps<"input"> & VariantProps<typeof inputVariants>) {
  const input = <input type={variant === "search" ? (type ?? "search") : type} data-slot="input" className={cn(inputVariants({ variant }), variant !== "search" && className)} {...useFieldControl(props)} />;
  if (variant !== "search") return input;
  return (
    <div data-slot="search" className={cn("relative w-full", className)}>
      <SearchIcon />
      {input}
    </div>
  );
}

export { Input, inputVariants };

Reference: agentcard-website src/app/globals.css (.v7-gate-field input, .v7-guide-search); live https://www.agentcard.sh/get-started (computed).