Zum Inhalt springen
UI Kit
Accent

Farbverlauf

Geschichtete Radialverläufe in Akzenttönen, ruhend oder langsam ziehend.

Eine von 6 Components in Backgrounds

4 Variants

Variants

Weiche Wolke

Eine blasse Tonwolke hinter der Mitte, ruhig genug für Gesundheit und Pflege.

Kräftig

Überlappende Töne füllen die Bühne und ziehen, der Farbton dreht sich leicht.

Ecken

Licht in zwei gegenüberliegenden Ecken, der Text steht in einer klaren Mitte. Ruhend.

Horizont

Die Töne steigen von der Unterkante auf wie ein früher Himmel.

Details

Was Farbverlauf mitbringt, aus Registry und Quellcode gelesen.

Kategorie
Backgrounds
Bereich
Elements
Variants
4
Version
1.0.0 · seit 1.0.0
Datei
components/ui/gradient-mesh.tsx
Bringt mit
utilskit
npm-Pakete
Keine
Sprachen
Texte kommen über Props
Studio
Im Code anpassen

Props

Die Schnittstelle GradientMeshProps aus components/ui/gradient-mesh.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.

  • variantoptional
    GradientMeshVariant

    softvividcornershorizon

    soft: a pale cloud of accent tones behind the middle. vivid: stronger, overlapping tones that fill the stage. corners: light gathers in two opposite corners, the middle stays clear. horizon: the tones rise from the bottom edge.

  • animateoptional
    boolean

    Let the tones drift slowly, default true

  • speedoptional
    number

    Seconds for one slow drift, default 26

  • parallaxoptional
    number

    Pixels the deepest tone drifts against the page over a full scroll pass, the others less. Default 40, 0 is off.

Barrierefreiheit

Aus dem Quellcode der Component abgeleitet.

  • Der Server rendert den Startzustand vollständig, JavaScript ergänzt nur die Interaktion.

Bewegung

  • Bewegt sich mit dem Scrollen, der Fortschritt folgt der Position auf der Seite.
  • Bringt eigene Animationen mit, ruhig getaktet und ohne Wackeln.
  • Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig.

Quellcode

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

Datei anzeigen, 176 Zeilen
components/ui/gradient-mesh.tsx
'use client';

import { useEffect, useRef } from 'react';
import { cn, prefersReducedMotion } from '@/lib/kit';

export type GradientMeshVariant = 'soft' | 'vivid' | 'corners' | 'horizon';

export interface GradientMeshProps {
  /**
   * soft: a pale cloud of accent tones behind the middle.
   * vivid: stronger, overlapping tones that fill the stage.
   * corners: light gathers in two opposite corners, the middle stays clear.
   * horizon: the tones rise from the bottom edge.
   */
  variant?: GradientMeshVariant;
  /** Let the tones drift slowly, default true */
  animate?: boolean;
  /** Seconds for one slow drift, default 26 */
  speed?: number;
  /** Pixels the deepest tone drifts against the page over a full scroll pass, the others less. Default 40, 0 is off. */
  parallax?: number;
  className?: string;
}

/*
 * Tones from the accent. The second tone turns the hue a little where the
 * browser supports relative colors, otherwise it is a lighter accent. Light
 * mode keeps every tone thin so a dark accent never turns into grey fog.
 */
const CSS = `
.kitfx-mesh {
  --kitfx-m1: color-mix(in oklab, var(--primary) 20%, transparent);
  --kitfx-m2: color-mix(in oklab, var(--primary) 12%, transparent);
  --kitfx-m3: color-mix(in oklab, var(--primary) 9%, transparent);
  --kitfx-m4: color-mix(in oklch, var(--background) 90%, transparent);
}
:where(.dark) .kitfx-mesh, .kitfx-mesh:where(.dark *) {
  --kitfx-m1: color-mix(in oklab, var(--primary) 46%, transparent);
  --kitfx-m2: color-mix(in oklch, color-mix(in oklab, var(--primary) 60%, var(--foreground)) 26%, transparent);
  --kitfx-m3: color-mix(in oklab, var(--primary) 30%, transparent);
  --kitfx-m4: color-mix(in oklch, var(--background) 45%, transparent);
}
@supports (color: oklch(from red l c h)) {
  /*
   * Lightness is clamped, so a near-black accent never paints dark fog in light
   * mode and a near-white one never turns to milk in dark mode. --kitfx-n says
   * how neutral the accent is (1 grey default, 0 real color): a neutral accent
   * gets a breath of chroma and its tones fan out in hue, a pearl sheen of
   * lavender, rose and sky instead of grey clouds. The second tone turns away from
   * yellow: warm accents (orange, rose) lean toward red, cool ones toward violet, so a
   * dark orange never turns olive.
   */
  .kitfx-mesh {
    --kitfx-n: clamp(0, 1 - c / 0.06, 1);
    --kitfx-m1: oklch(from var(--primary) calc(max(l, 0.64) + 0.06 * var(--kitfx-n)) max(c, 0.085) h / calc(0.22 + 0.1 * var(--kitfx-n)));
    --kitfx-m2: oklch(from var(--primary) calc(max(l, 0.68) + 0.06 * var(--kitfx-n)) max(c, 0.075) calc(h - 38 * clamp(-1, cos((h - 75) * 1deg) * 3, 1) + 26 * var(--kitfx-n)) / calc(0.16 + 0.12 * var(--kitfx-n)));
    --kitfx-m3: oklch(from var(--primary) calc(max(l, 0.72) + 0.04 * var(--kitfx-n)) max(c, 0.07) calc(h - 80 * var(--kitfx-n)) / calc(0.14 + 0.12 * var(--kitfx-n)));
  }
  :where(.dark) .kitfx-mesh, .kitfx-mesh:where(.dark *) {
    --kitfx-m1: oklch(from var(--primary) calc(min(l, 0.7) - 0.02 * var(--kitfx-n)) max(c, 0.08) h / calc(0.42 + 0.04 * var(--kitfx-n)));
    --kitfx-m2: oklch(from var(--primary) min(l, 0.66) max(c, 0.075) calc(h - 38 * clamp(-1, cos((h - 75) * 1deg) * 3, 1) + 26 * var(--kitfx-n)) / 0.3);
    --kitfx-m3: oklch(from var(--primary) calc(min(l, 0.6) + 0.06 * var(--kitfx-n)) max(c, 0.07) calc(h - 80 * var(--kitfx-n)) / calc(0.3 + 0.04 * var(--kitfx-n)));
  }
}
@keyframes kitfx-mesh-drift {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  33% { transform: translate3d(var(--kitfx-dx), var(--kitfx-dy), 0) scale(1.08); }
  66% { transform: translate3d(calc(var(--kitfx-dx) * -0.6), calc(var(--kitfx-dy) * 0.5), 0) scale(0.96); }
}
.kitfx-mesh * { animation-play-state: var(--kitfx-play, running); }
@media (prefers-reduced-motion: reduce) { .kitfx-mesh * { animation: none !important; } .kitfx-mesh-depth { transform: none !important; } }
`;

interface Blob { x: number; y: number; w: number; h: number; tone: 1 | 2 | 3 | 4; dx: number; dy: number; t: number }

const LAYOUT: Record<GradientMeshVariant, Blob[]> = {
  soft: [
    { x: 30, y: 38, w: 55, h: 70, tone: 1, dx: 6, dy: 4, t: 1 },
    { x: 68, y: 46, w: 50, h: 64, tone: 2, dx: -5, dy: 5, t: 1.3 },
    { x: 50, y: 70, w: 60, h: 50, tone: 3, dx: 4, dy: -4, t: 1.6 },
    { x: 50, y: 42, w: 34, h: 40, tone: 4, dx: -3, dy: 3, t: 1.2 },
  ],
  vivid: [
    { x: 18, y: 28, w: 62, h: 80, tone: 1, dx: 8, dy: 6, t: 1 },
    { x: 80, y: 30, w: 58, h: 76, tone: 2, dx: -7, dy: 6, t: 1.25 },
    { x: 60, y: 82, w: 70, h: 70, tone: 1, dx: -5, dy: -6, t: 1.5 },
    { x: 26, y: 84, w: 54, h: 60, tone: 3, dx: 6, dy: -4, t: 1.15 },
    { x: 50, y: 48, w: 40, h: 46, tone: 4, dx: 3, dy: 3, t: 1.4 },
  ],
  corners: [
    { x: 4, y: 0, w: 58, h: 80, tone: 1, dx: 5, dy: 5, t: 1 },
    { x: 18, y: 10, w: 36, h: 50, tone: 2, dx: 4, dy: 3, t: 1.3 },
    { x: 96, y: 100, w: 58, h: 80, tone: 1, dx: -5, dy: -5, t: 1.15 },
    { x: 82, y: 92, w: 36, h: 50, tone: 2, dx: -4, dy: -3, t: 1.45 },
  ],
  horizon: [
    { x: 22, y: 104, w: 64, h: 70, tone: 1, dx: 6, dy: -4, t: 1 },
    { x: 54, y: 108, w: 70, h: 64, tone: 2, dx: -6, dy: -5, t: 1.3 },
    { x: 84, y: 102, w: 60, h: 70, tone: 3, dx: -4, dy: -4, t: 1.6 },
    { x: 50, y: 96, w: 46, h: 30, tone: 1, dx: 4, dy: -2, t: 1.15 },
  ],
};

/**
 * Scroll pass. While `on`, writes --kitfx-p on the element: 0 when its top
 * meets the bottom of the window, 1 when its bottom leaves at the top. Never
 * with reduced motion, the CSS then falls back to the resting value.
 */
function useScrollPass(ref: { current: HTMLElement | null }, on: boolean) {
  useEffect(() => {
    const el = ref.current;
    if (!el || !on || prefersReducedMotion()) return;
    let frame = 0;
    const update = () => {
      frame = 0;
      const r = el.getBoundingClientRect();
      const p = Math.min(1, Math.max(0, (window.innerHeight - r.top) / (r.height + window.innerHeight)));
      el.style.setProperty('--kitfx-p', p.toFixed(4));
    };
    const onScroll = () => {
      if (!frame) frame = requestAnimationFrame(update);
    };
    update();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => {
      cancelAnimationFrame(frame);
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      el.style.removeProperty('--kitfx-p');
    };
  }, [ref, on]);
}

/**
 * Layered radial gradients in accent tones, still or drifting slowly.
 * Fills its parent (parent `relative isolate overflow-hidden`, content after
 * it with `relative`). Only transforms move, paused out of view, still with
 * reduced motion.
 */
export function GradientMesh({ variant = 'soft', animate = true, speed = 26, parallax = 40, className }: GradientMeshProps) {
  const ref = useRef<HTMLDivElement>(null);
  useScrollPass(ref, parallax !== 0);
  const blobs = LAYOUT[variant];
  useEffect(() => {
    const el = ref.current;
    if (!el || !animate || typeof IntersectionObserver === 'undefined') return;
    const io = new IntersectionObserver(([e]) => el.style.setProperty('--kitfx-play', e.isIntersecting ? 'running' : 'paused'));
    io.observe(el);
    return () => io.disconnect();
  }, [animate]);

  return (
    <div ref={ref} aria-hidden data-clip-ok className={cn('kitfx-mesh pointer-events-none absolute inset-0 overflow-hidden', className)}>
      <style dangerouslySetInnerHTML={{ __html: CSS }} />
      {blobs.map((b, i) => (
        // Every tone on its own depth, the later ones nearer, so the cloud gains a little volume when the page moves
        <div key={i} className="kitfx-mesh-depth absolute inset-0" style={parallax ? { transform: `translate3d(0, calc((var(--kitfx-p, 0.5) - 0.5) * ${Math.round(parallax * (0.45 + (0.55 * i) / Math.max(1, blobs.length - 1)))}px), 0)` } : undefined}>
        <div
          className="absolute rounded-full"
          style={{
            left: `${b.x - b.w / 2}%`,
            top: `${b.y - b.h / 2}%`,
            width: `${b.w}%`,
            height: `${b.h}%`,
            background: `radial-gradient(closest-side, var(--kitfx-m${b.tone}), transparent)`,
            ['--kitfx-dx' as string]: `${b.dx}%`,
            ['--kitfx-dy' as string]: `${b.dy}%`,
            animation: animate ? `kitfx-mesh-drift ${Math.round(speed * b.t)}s ease-in-out ${-i * 4}s infinite` : undefined,
          }}
        />
        </div>
      ))}
    </div>
  );
}

Häufige Fragen

Wie installiere ich Farbverlauf?

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

Welche Varianten hat Farbverlauf?

4 Varianten, Weiche Wolke, Kräftig, Ecken und Horizont. Du wählst sie mit der Prop variant, zum Beispiel variant="vivid".

Kann ein KI-Assistent Farbverlauf einbauen?

Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Farbverlauf mit aiw_components und installieren sie mit aiw_add @ai-website/gradient-mesh. 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