Zum Inhalt springen
UI Kit
Accent

Suchfeld

Das Suchfeld mit Tastenkürzel, Leeren-Knopf, Escape zum Leeren, Ladezustand und Absendeknopf.

Eine von 9 Components in Forms und Inputs

2 Variants

Variants

Suchfeld

Strg K oder ⌘K fokussiert es von überall und die Tasten drücken sich. Der Fokus weitet das Feld ein wenig, eine Eingabe bekommt einen Leeren-Knopf, eine laufende Suche einen Spinner.

In einer Kopfzeile

Der weiche Stil ohne Rand sitzt ruhig in Leisten und wird beim Fokus hell.

Details

Was Suchfeld mitbringt, aus Registry und Quellcode gelesen.

Bereich
Elements
Variants
2
Version
1.0.0 · seit 1.0.0
Datei
components/ui/search-field.tsx
Bringt mit
utilskiticoninputloading-button
npm-Pakete
Keine
Sprachen
Englisch und Deutsch, 5 eigene Texte
Studio
Im Code anpassen

Props

Die Schnittstelle SearchFieldProps aus components/ui/search-field.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch. Nimmt außerdem alle Props von Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'onChange' | 'value' | 'defaultValue'> an.

  • valueoptional
    string
  • defaultValueoptional
    string
  • onValueChangeoptional
    (value: string) => void
  • onSearchoptional
    (value: string) => void

    Called on Enter and on the submit button

  • sizeoptional
    ControlSize
  • shortcutoptional
    string

    A key that focuses the field from anywhere, shown as a hint: "k" means Ctrl K or ⌘K, "/" means slash

  • loadingoptional
    boolean

    Shows a spinner instead of the search icon

  • submitLabeloptional
    string

    A submit button at the end

  • variantoptional
    'default' | 'soft'

    soft: filled surface without border, for headers and toolbars

  • labeloptional
    string

    Accessible name, when there is no visible label

Eingebaute Texte

Diese Texte schreibt Suchfeld selbst. Die Sprache folgt dem KitProvider, über die Prop labels ersetzt du einzelne.

Alle 5 Texte anzeigen
labelEnglisch SearchDeutsch Suche
placeholderEnglisch SearchDeutsch Suchen
clearEnglisch Clear searchDeutsch Suche leeren
submitEnglisch SearchDeutsch Suchen
searchingEnglisch SearchingDeutsch Sucht

Barrierefreiheit

Aus dem Quellcode der Component abgeleitet.

  • Suchfeld reagiert selbst auf die Tasten Escape und Leertaste.
  • Setzt aria-label, damit Screenreader Zweck und Zustand ansagen.
  • Nutzt die Rolle search.
  • Der Server rendert den Startzustand vollständig, JavaScript ergänzt nur die Interaktion.

Bewegung

  • Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig. 3 Stellen im Code schalten Bewegung dann ab.
  • Ohne Animationen, sie steht sofort ruhig da.

Quellcode

Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.

Datei anzeigen, 165 Zeilen
components/ui/search-field.tsx
'use client';

import { useEffect, useRef, useState } from 'react';
import { cn, prefersReducedMotion, useTexts } from '@/lib/kit';
import { Icon } from '@/components/ui/icon';
import { CONTROL_HEIGHT, InlineButton, controlBox, type ControlSize } from '@/components/ui/input';
import { Spinner } from '@/components/ui/loading-button';

const TEXTS = {
  en: { label: 'Search', placeholder: 'Search', clear: 'Clear search', submit: 'Search', searching: 'Searching' },
  de: { label: 'Suche', placeholder: 'Suchen', clear: 'Suche leeren', submit: 'Suchen', searching: 'Sucht' },
};

export interface SearchFieldProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'onChange' | 'value' | 'defaultValue'> {
  value?: string;
  defaultValue?: string;
  onValueChange?: (value: string) => void;
  /** Called on Enter and on the submit button */
  onSearch?: (value: string) => void;
  size?: ControlSize;
  /** A key that focuses the field from anywhere, shown as a hint: "k" means Ctrl K or ⌘K, "/" means slash */
  shortcut?: string;
  /** Shows a spinner instead of the search icon */
  loading?: boolean;
  /** A submit button at the end */
  submitLabel?: string;
  /** soft: filled surface without border, for headers and toolbars */
  variant?: 'default' | 'soft';
  /** Accessible name, when there is no visible label */
  label?: string;
  labels?: Partial<(typeof TEXTS)['en']>;
  className?: string;
}

function Kbd({ children }: { children: React.ReactNode }) {
  return <kbd data-cap className="grid h-[22px] min-w-[22px] place-items-center rounded-[5px] border border-border bg-surface px-1.5 font-sans text-[11px] font-medium text-muted-foreground shadow-[0_1px_0_var(--border)]">{children}</kbd>;
}

/** The key caps press down one after the other, as if the shortcut was typed on them */
function pressCaps(root: HTMLElement | null) {
  if (!root || prefersReducedMotion()) return;
  root.querySelectorAll<HTMLElement>('[data-cap]').forEach((cap, i) => {
    if (typeof cap.animate !== 'function') return;
    cap.animate(
      [
        { transform: 'translateY(0)', boxShadow: '0 1px 0 var(--border)' },
        { transform: 'translateY(1.5px) scale(0.94)', boxShadow: '0 0 0 var(--border)', offset: 0.35 },
        { transform: 'translateY(0)', boxShadow: '0 1px 0 var(--border)' },
      ],
      { duration: 260, delay: i * 60, easing: 'cubic-bezier(0.2,0.7,0.2,1)' },
    );
  });
}

/**
 * The search box: icon, clear button, Escape to clear, a keyboard shortcut and a loading state.
 *   <SearchField shortcut="k" placeholder="Search invoices" onSearch={run} />
 */
export function SearchField({ value: valueProp, defaultValue, onValueChange, onSearch, size = 'md', shortcut, loading, submitLabel, variant = 'default', label, labels, className, placeholder, ...rest }: SearchFieldProps) {
  const t = useTexts(TEXTS, labels);
  const [inner, setInner] = useState(defaultValue ?? '');
  const value = valueProp ?? inner;
  const ref = useRef<HTMLInputElement>(null);
  const hintRef = useRef<HTMLSpanElement>(null);
  const [mac, setMac] = useState(true);

  useEffect(() => {
    setMac(/Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent));
    if (!shortcut) return;
    const onKey = (e: KeyboardEvent) => {
      const target = e.target as HTMLElement;
      const typing = target.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName);
      if (shortcut === '/' ? e.key === '/' && !typing : e.key.toLowerCase() === shortcut.toLowerCase() && (e.metaKey || e.ctrlKey)) {
        e.preventDefault();
        pressCaps(hintRef.current);
        ref.current?.focus();
        ref.current?.select();
      }
    };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [shortcut]);

  const set = (v: string) => {
    setInner(v);
    onValueChange?.(v);
  };

  return (
    <form
      role="search"
      onSubmit={(e) => {
        e.preventDefault();
        onSearch?.(value);
      }}
      className={cn(
        // Focus widens the box a hair and lights the icon, the ring fades in from the shared control box
        'group/search relative flex w-full min-w-0 items-center focus-within:scale-[1.012] motion-reduce:focus-within:scale-100',
        CONTROL_HEIGHT[size],
        controlBox,
        variant === 'soft' && 'border-transparent bg-muted shadow-none hover:border-transparent hover:bg-[color-mix(in_oklab,var(--foreground)_6%,var(--muted))] focus-within:bg-background',
        submitLabel && 'pe-1',
        className,
      )}
    >
      <span className={cn('grid shrink-0 place-items-center text-subtle-foreground transition-[color,scale] duration-300 ease-[var(--ease-kit)] group-focus-within/search:scale-110 group-focus-within/search:text-[color-mix(in_oklab,var(--primary)_70%,var(--foreground))]', size === 'sm' ? 'ms-3' : 'ms-3.5')}>
        {loading ? <Spinner size={16} label={t.searching} /> : <Icon name="search" size={size === 'lg' ? 18 : 16} />}
      </span>
      <input
        {...rest}
        ref={ref}
        type="search"
        value={value}
        placeholder={placeholder ?? t.placeholder}
        aria-label={label ?? t.label}
        enterKeyHint="search"
        onChange={(e) => set(e.target.value)}
        onKeyDown={(e) => {
          if (e.key === 'Escape' && value) {
            e.preventDefault();
            set('');
          }
          rest.onKeyDown?.(e);
        }}
        className="h-full w-0 min-w-0 flex-1 bg-transparent ps-2.5 pe-2 text-inherit outline-none placeholder:text-subtle-foreground [&::-webkit-search-cancel-button]:appearance-none"
      />
      {value ? (
        <span className="pe-1.5 transition-[opacity,scale] duration-200 starting:scale-50 starting:opacity-0 motion-reduce:transition-none">
          <InlineButton
            label={t.clear}
            icon="x"
            onClick={() => {
              set('');
              ref.current?.focus();
            }}
          />
        </span>
      ) : (
        shortcut && (
          <span ref={hintRef} aria-hidden className="flex shrink-0 items-center gap-1 pe-2.5 transition-[opacity,translate] duration-300 ease-[var(--ease-kit)] group-focus-within/search:translate-x-1 group-focus-within/search:opacity-40 max-sm:hidden">
            {shortcut === '/' ? (
              <Kbd>/</Kbd>
            ) : (
              <>
                <Kbd>{mac ? '⌘' : 'Ctrl'}</Kbd>
                <Kbd>{shortcut.toUpperCase()}</Kbd>
              </>
            )}
          </span>
        )
      )}
      {submitLabel && (
        <button
          type="submit"
          className={cn(
            'inline-flex shrink-0 items-center rounded-md bg-primary px-3.5 text-[13px] font-medium text-primary-foreground shadow-[inset_0_1px_0_rgb(255_255_255/0.18)] transition-[background-color,scale] duration-200 hover:bg-[color-mix(in_oklab,var(--primary)_88%,var(--background))] active:scale-[0.96] active:duration-100 motion-reduce:active:scale-100',
            size === 'sm' ? 'h-7' : size === 'md' ? 'h-9' : 'h-10',
          )}
        >
          {submitLabel}
        </button>
      )}
    </form>
  );
}

Häufige Fragen

Wie installiere ich Suchfeld?

Mit aiw add @ai-website/search-field. Der Befehl legt die Datei components/ui/search-field.tsx in dein Projekt, der Code gehört danach dir. Mit dabei sind icon, input und loading-button. Weitere npm-Pakete braucht sie nicht.

Welche Varianten hat Suchfeld?

2 Varianten, Suchfeld und In einer Kopfzeile.

Kann ich die Texte von Suchfeld übersetzen?

Ja. Suchfeld bringt 5 eigene Texte auf Deutsch und Englisch mit, etwa „Suche“, „Suchen“ und „Suche leeren“. Die Sprache folgt dem KitProvider, einzelne Texte ersetzt du über die Prop labels.

Kann ein KI-Assistent Suchfeld einbauen?

Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Suchfeld mit aiw_components und installieren sie mit aiw_add @ai-website/search-field. Nenn im Prompt die Variante, etwa variant="soft", dann schreibt der Assistent deine Inhalte direkt in die Props.

Oft gesucht

Direkt zu

In die Zwischenablage kopiert