Zum Inhalt springen
UI Kit
Accent

Spot-Illustrationen

Sechzehn kleine Zeichnungen in einem Stil für Features, leere Zustände, Schritte und Bestätigungen. Sicherheit, Tempo, Wachstum, Team, Lieferung, Hilfe, Suche, Posteingang, Dateien, Kalender, Zahlung, Erfolg, Offline, Hochladen, Ort und Ideen. Sie nehmen die Theme-Farben an und setzen sich beim Hereinscrollen zusammen.

Eine von 5 Components in Graphics

5 Variants

Variants

Das Set

Alle sechzehn Motive mit Namen. Jedes setzt sich beim Hereinscrollen Teil für Teil zusammen, das Akzentteil hebt sich beim Hover.

Im Studio öffnen

Feature-Karten

Drei Features mit je einer Zeichnung. Die Karten steigen nacheinander auf, Hover hebt die Karte und bewegt die Zeichnung.

Im Studio öffnen

Leere Zustände

Eine Suche ohne Treffer, ein leerer Posteingang und ein erster Upload, je mit Zeichnung, einer klaren Zeile und dem nächsten Schritt. Der Umschalter zeigt sie nacheinander.

Im Studio öffnen

Schritte

Drei Schritte mit Zeichnungen, verbunden durch eine Linie, die sich beim Scrollen füllt. Telefone stapeln sie.

Im Studio öffnen

Bestätigung

Eine große Zeichnung über der Nachricht einer Bestätigungsseite, unter weichem Licht. Erst setzt sich die Zeichnung zusammen, der Rest folgt.

Im Studio öffnen

Details

Was Spot-Illustrationen mitbringt, aus Registry und Quellcode gelesen.

Kategorie
Graphics
Bereich
Visuals
Variants
5
Version
1.0.0 · seit 1.1.0
Datei
components/ui/spot-illustrations.tsx
Bringt mit
utilskit
npm-Pakete
Keine
Sprachen
Texte kommen über Props

Props

Die Schnittstelle SpotIllustrationProps aus components/ui/spot-illustrations.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.

  • kindPflicht
    SpotKind

    securityspeedgrowthteamdeliverysupportsearchinboxfilescalendarpaymentssuccessofflineuploadlocationideas

  • sizeoptional
    number | string

    Width in pixels or any CSS length, the height follows (5 : 4). Default 160.

  • animateoptional
    boolean

    Assemble when it scrolls into view. Default true.

  • labeloptional
    string

    Accessible name. Without it the drawing is decoration.

Barrierefreiheit

Aus dem Quellcode der Component abgeleitet.

  • Setzt aria-label, damit Screenreader Zweck und Zustand ansagen.
  • Nutzt die Rolle label.
  • Kommt ohne Client-State aus. Der Server schreibt sie vollständig, sie ist auch ohne JavaScript lesbar.

Bewegung

  • Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig.
  • Ohne Animationen, sie steht sofort ruhig da.

Quellcode

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

Datei anzeigen, 236 Zeilen
components/ui/spot-illustrations.tsx
'use client';

import { cn, useInView } from '@/lib/kit';

/*
 * Small spot illustrations for features, empty states and steps. Sixteen
 * motifs in one style: a soft card ground, one accent shape, quiet lines in
 * the text color and a few dots of light. They take the theme colors, so they
 * sit in light and dark mode and follow the accent.
 *
 * Each motif assembles itself once it scrolls into view (the parts rise one
 * after another) and its accent part moves a little on hover of the nearest
 * .group parent. The server renders the finished drawing.
 */

export type SpotKind =
  | 'security'
  | 'speed'
  | 'growth'
  | 'team'
  | 'delivery'
  | 'support'
  | 'search'
  | 'inbox'
  | 'files'
  | 'calendar'
  | 'payments'
  | 'success'
  | 'offline'
  | 'upload'
  | 'location'
  | 'ideas';

export const SPOT_KINDS: SpotKind[] = ['security', 'speed', 'growth', 'team', 'delivery', 'support', 'search', 'inbox', 'files', 'calendar', 'payments', 'success', 'offline', 'upload', 'location', 'ideas'];

export interface SpotIllustrationProps {
  kind: SpotKind;
  /** Width in pixels or any CSS length, the height follows (5 : 4). Default 160. */
  size?: number | string;
  /** Assemble when it scrolls into view. Default true. */
  animate?: boolean;
  /** Accessible name. Without it the drawing is decoration. */
  label?: string;
  className?: string;
}

const A = 'var(--primary)';
const A2 = 'color-mix(in oklab, var(--primary) 30%, var(--card))';
const G = 'color-mix(in oklab, var(--foreground) 6%, var(--card))';
const L = 'color-mix(in oklab, var(--foreground) 16%, var(--card))';
const S = 'color-mix(in oklab, var(--foreground) 55%, transparent)';
const W = 'var(--card)';

const STYLES = `
.ksp-part{transition:opacity .6s var(--ease-kit),transform .7s var(--ease-kit);transition-delay:calc(var(--i,0)*90ms)}
.ksp-off .ksp-part{opacity:0;transform:translateY(8px) scale(.96)}
.ksp-acc{transition:transform .6s var(--ease-kit);transform-box:fill-box;transform-origin:center}
.group:hover .ksp-acc,.ksp-root:hover .ksp-acc{transform:translateY(-3px) rotate(-4deg)}
@media (prefers-reduced-motion:reduce){.ksp-part,.ksp-acc{transition:none}.ksp-off .ksp-part{opacity:1;transform:none}}
`;

/** One part of a motif, with its place in the assembly order */
const P = ({ i, acc, children }: { i: number; acc?: boolean; children: React.ReactNode }) => (
  <g className="ksp-part" style={{ '--i': i } as React.CSSProperties}>
    {acc ? <g className="ksp-acc">{children}</g> : children}
  </g>
);

const stroke = { stroke: S, strokeWidth: 2, strokeLinecap: 'round' as const, strokeLinejoin: 'round' as const, fill: 'none' };

function art(kind: SpotKind): React.ReactNode {
  switch (kind) {
    case 'security':
      return (
        <>
          <P i={0}><rect x="22" y="22" width="76" height="60" rx="10" fill={G} /></P>
          <P i={1} acc><path d="M60 26 80 34v14c0 13-8 22-20 28-12-6-20-15-20-28V34Z" fill={A} /></P>
          <P i={2}><path d="m51 50 6 6 12-13" stroke={W} strokeWidth="3.4" strokeLinecap="round" strokeLinejoin="round" fill="none" /></P>
          <P i={3}><circle cx="96" cy="22" r="3" fill={A2} /><circle cx="22" cy="78" r="2" fill={L} /></P>
        </>
      );
    case 'speed':
      return (
        <>
          <P i={0}><circle cx="60" cy="56" r="30" fill={G} /></P>
          <P i={1}><path d="M38 64a22 22 0 1 1 44 0" {...stroke} /><path d="M42 50h3M60 36v3M78 50h-3" {...stroke} /></P>
          <P i={2} acc><path d="M60 64 74 44" stroke={A} strokeWidth="4" strokeLinecap="round" /><circle cx="60" cy="64" r="5" fill={A} /></P>
          <P i={3}><path d="M12 30h14M8 40h12M14 50h8" stroke={A2} strokeWidth="3" strokeLinecap="round" /></P>
        </>
      );
    case 'growth':
      return (
        <>
          <P i={0}><rect x="18" y="20" width="84" height="64" rx="10" fill={G} /></P>
          <P i={1}>{[0, 1, 2, 3].map((k) => <rect key={k} x={30 + k * 16} y={66 - k * 9} width="9" height={10 + k * 9} rx="2.5" fill={L} />)}</P>
          <P i={2} acc><path d="M28 60 46 48l12 6 22-20" stroke={A} strokeWidth="3.4" strokeLinecap="round" strokeLinejoin="round" fill="none" /><path d="M72 32h9v9" stroke={A} strokeWidth="3.4" strokeLinecap="round" strokeLinejoin="round" fill="none" /></P>
        </>
      );
    case 'team':
      return (
        <>
          <P i={0}><rect x="16" y="54" width="88" height="28" rx="14" fill={G} /></P>
          <P i={1}><circle cx="38" cy="44" r="10" fill={L} /><path d="M24 74c2-9 8-14 14-14s12 5 14 14" fill={L} /></P>
          <P i={2}><circle cx="82" cy="44" r="10" fill={L} /><path d="M68 74c2-9 8-14 14-14s12 5 14 14" fill={L} /></P>
          <P i={3} acc><circle cx="60" cy="38" r="12" fill={A} /><path d="M42 74c2-12 9-18 18-18s16 6 18 18" fill={A} /></P>
        </>
      );
    case 'delivery':
      return (
        <>
          <P i={0}><path d="M10 78h100" {...stroke} /></P>
          <P i={1} acc><rect x="22" y="36" width="46" height="34" rx="4" fill={A} /><path d="M68 46h16l10 12v12H68Z" fill={A2} /></P>
          <P i={2}><circle cx="38" cy="74" r="7" fill={W} stroke={S} strokeWidth="3" /><circle cx="82" cy="74" r="7" fill={W} stroke={S} strokeWidth="3" /></P>
          <P i={3}><path d="M6 44h10M2 54h12" stroke={A2} strokeWidth="3" strokeLinecap="round" /></P>
        </>
      );
    case 'support':
      return (
        <>
          <P i={0}><rect x="16" y="22" width="56" height="36" rx="12" fill={G} /><path d="M30 58v10l10-10" fill={G} /></P>
          <P i={1}><path d="M28 36h32M28 45h20" {...stroke} /></P>
          <P i={2} acc><rect x="52" y="44" width="52" height="32" rx="12" fill={A} /><path d="M92 76v9l-9-9" fill={A} /></P>
          <P i={3}><circle cx="66" cy="60" r="2.6" fill={W} /><circle cx="78" cy="60" r="2.6" fill={W} /><circle cx="90" cy="60" r="2.6" fill={W} /></P>
        </>
      );
    case 'search':
      return (
        <>
          <P i={0}><rect x="16" y="20" width="70" height="56" rx="10" fill={G} /></P>
          <P i={1}><path d="M28 34h34M28 44h22M28 54h28" {...stroke} /></P>
          <P i={2} acc><circle cx="74" cy="56" r="15" fill={W} stroke={A} strokeWidth="4" /><path d="m85 67 13 13" stroke={A} strokeWidth="5" strokeLinecap="round" /></P>
        </>
      );
    case 'inbox':
      return (
        <>
          <P i={0}><path d="M24 50 34 26h52l10 24v26H24Z" fill={G} /></P>
          <P i={1}><path d="M24 50h22l4 8h20l4-8h22" {...stroke} /></P>
          <P i={2} acc><rect x="42" y="14" width="36" height="26" rx="4" fill={A} /><path d="m44 18 16 12 16-12" stroke={W} strokeWidth="2.6" fill="none" strokeLinejoin="round" /></P>
          <P i={3}><circle cx="96" cy="20" r="3" fill={A2} /></P>
        </>
      );
    case 'files':
      return (
        <>
          <P i={0}><rect x="20" y="30" width="44" height="54" rx="6" fill={L} transform="rotate(-8 42 57)" /></P>
          <P i={1}><path d="M46 18h28l14 14v50H46Z" fill={G} /><path d="M74 18v14h14" fill={L} /></P>
          <P i={2}><path d="M54 46h26M54 55h26M54 64h16" {...stroke} /></P>
          <P i={3} acc><circle cx="88" cy="76" r="11" fill={A} /><path d="m83 76 4 4 7-8" stroke={W} strokeWidth="2.8" fill="none" strokeLinecap="round" strokeLinejoin="round" /></P>
        </>
      );
    case 'calendar':
      return (
        <>
          <P i={0}><rect x="22" y="22" width="76" height="62" rx="10" fill={G} /><rect x="22" y="22" width="76" height="16" rx="8" fill={L} /></P>
          <P i={1}><path d="M38 16v12M82 16v12" {...stroke} /></P>
          <P i={2}>{Array.from({ length: 8 }, (_, k) => <rect key={k} x={32 + (k % 4) * 15} y={46 + Math.floor(k / 4) * 15} width="9" height="9" rx="2.5" fill={L} />)}</P>
          <P i={3} acc><rect x="62" y="61" width="11" height="11" rx="3" fill={A} /></P>
        </>
      );
    case 'payments':
      return (
        <>
          <P i={0}><rect x="14" y="30" width="70" height="44" rx="8" fill={G} /><path d="M14 42h70" stroke={L} strokeWidth="7" /></P>
          <P i={1} acc><rect x="36" y="22" width="70" height="44" rx="8" fill={A} /><path d="M36 34h70" stroke="color-mix(in oklab, var(--primary) 70%, black)" strokeWidth="7" /></P>
          <P i={2}><rect x="46" y="48" width="18" height="8" rx="2" fill={W} opacity="0.85" /><circle cx="90" cy="54" r="5" fill={W} opacity="0.6" /><circle cx="96" cy="54" r="5" fill={W} opacity="0.85" /></P>
        </>
      );
    case 'success':
      return (
        <>
          <P i={0}><circle cx="60" cy="52" r="32" fill={G} /></P>
          <P i={1} acc><circle cx="60" cy="52" r="20" fill={A} /><path d="m51 52 6 6 12-13" stroke={W} strokeWidth="3.6" fill="none" strokeLinecap="round" strokeLinejoin="round" /></P>
          <P i={2}><path d="M22 22l4 4M98 22l-4 4M16 60h6M98 60h6M30 90l3-4M90 90l-3-4" stroke={A2} strokeWidth="3" strokeLinecap="round" /></P>
        </>
      );
    case 'offline':
      return (
        <>
          <P i={0}><circle cx="60" cy="52" r="34" fill={G} /></P>
          <P i={1}><path d="M34 46a38 38 0 0 1 52 0M42 55a26 26 0 0 1 36 0" {...stroke} /><circle cx="60" cy="66" r="4" fill={S} /></P>
          <P i={2} acc><path d="M32 26 88 82" stroke={A} strokeWidth="4.4" strokeLinecap="round" /></P>
        </>
      );
    case 'upload':
      return (
        <>
          <P i={0}><path d="M32 74a18 18 0 0 1 2-36 24 24 0 0 1 46-4 20 20 0 0 1 8 40Z" fill={G} /></P>
          <P i={1} acc><path d="M60 72V44" stroke={A} strokeWidth="4.4" strokeLinecap="round" /><path d="m48 54 12-12 12 12" stroke={A} strokeWidth="4.4" fill="none" strokeLinecap="round" strokeLinejoin="round" /></P>
          <P i={2}><circle cx="96" cy="24" r="3" fill={A2} /><circle cx="20" cy="30" r="2" fill={L} /></P>
        </>
      );
    case 'location':
      return (
        <>
          <P i={0}><path d="M14 74 40 64l40 10 26-10v14l-26 10-40-10-26 10Z" fill={G} /></P>
          <P i={1}><path d="M40 64v14M80 74v14" stroke={L} strokeWidth="2" /></P>
          <P i={2} acc><path d="M60 70c-12-14-18-23-18-31a18 18 0 0 1 36 0c0 8-6 17-18 31Z" fill={A} /><circle cx="60" cy="39" r="6.5" fill={W} /></P>
        </>
      );
    default:
      return (
        <>
          <P i={0}><circle cx="60" cy="44" r="30" fill={G} /></P>
          <P i={1} acc><path d="M60 22a18 18 0 0 0-10 33v7h20v-7a18 18 0 0 0-10-33Z" fill={A} /><rect x="51" y="66" width="18" height="6" rx="3" fill={A2} /></P>
          <P i={2}><path d="M60 8v6M32 20l4 4M88 20l-4 4M24 44h6M90 44h6" stroke={A2} strokeWidth="3" strokeLinecap="round" /></P>
        </>
      );
  }
}

/**
 * A small drawing for a feature, an empty state or a step.
 *   <SpotIllustration kind="security" size={140} />
 */
export function SpotIllustration({ kind, size = 160, animate = true, label, className }: SpotIllustrationProps) {
  const [ref, inView] = useInView<SVGSVGElement>('0px 0px -10% 0px');
  return (
    <>
      <style href="kit-spot-illustrations" precedence="default">
        {STYLES}
      </style>
      <svg
        ref={ref}
        viewBox="0 0 120 96"
        role={label ? 'img' : undefined}
        aria-label={label}
        aria-hidden={label ? undefined : true}
        className={cn('ksp-root h-auto shrink-0 overflow-visible', animate && !inView && 'ksp-off', className)}
        style={{ width: size }}
      >
        {art(kind)}
      </svg>
    </>
  );
}

Häufige Fragen

Wie installiere ich Spot-Illustrationen?

Mit aiw add @ai-website/spot-illustrations. Der Befehl legt die Datei components/ui/spot-illustrations.tsx in dein Projekt, der Code gehört danach dir. Weitere npm-Pakete braucht sie nicht.

Welche Varianten hat Spot-Illustrationen?

5 Varianten, Das Set, Feature-Karten, Leere Zustände, Schritte und Bestätigung.

Kann ein KI-Assistent Spot-Illustrationen einbauen?

Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Spot-Illustrationen mit aiw_components und installieren sie mit aiw_add @ai-website/spot-illustrations.

Kann ich Spot-Illustrationen ohne Code anpassen?

Ja, im Studio. Alle 5 Varianten lassen sich dort bearbeiten, Texte, Listen und Schalter. Das Studio schreibt den passenden SpotIllustration-Code zum Kopieren.

Oft gesucht

Direkt zu

In die Zwischenablage kopiert