Zum Inhalt springen
UI Kit
Accent

Tiefenebenen

Ein Bild, das sich bei Hover, Fokus, per Knopf oder beim Scrollen in seine Ebenen auffächert. Jede Ebene behält ihren Namen als echten Text daneben, ohne JavaScript öffnet sich der Fächer trotzdem beim Hover.

4 VariantsEine von 6 Components in 3D-Flächen

Jede Ebene mit ihrem Namen

Tiefenebenen zerlegen ein Bild in die Schichten, aus denen es besteht. Jede Ebene behält ihren Namen als echten Text daneben, die Idee kommt also an, bevor sich etwas bewegt, und ohne JavaScript öffnet sich der Fächer trotzdem beim Hover.

„Oberfläche aufgefächert“ fächert eine Produktkarte seitlich in Bild, Fläche, Inhalt und Aktion auf, die Legende hebt eine Ebene an, ein Knopf öffnet den Fächer auf Touchscreens. „Ebenen beim Scrollen“ dreht dieselbe Karte auseinander, sobald die Section die Fenstermitte erreicht, und lässt sie so stehen. Das „Diorama-Fenster“ zeigt eine Landschaft in vier Tiefenebenen, die sich mit der Maus gegeneinander verschieben, die Karte steht vorn auf fester Fläche. Der „Element-Inspektor“ dreht ein Element zur Seite und zerlegt es in Schatten, Fläche, Rahmen, Inhalt und Fokusring.

Oberfläche und Scroll-Fassung zeigen, woraus ein Produkt besteht, das Diorama trägt eine Stimmung, der Inspektor passt zu einem Designsystem oder einem Leitfaden. Halte Ebenennamen bei zwei oder drei Wörtern. Ein Fächer pro Section hält den Effekt besonders.

Variants

Oberfläche aufgefächertNeu in 1.2

Eine Produktkarte fächert sich beim Hover seitlich in Bild, Fläche, Inhalt und Aktion auf. Die Legende hebt eine Ebene an, ein Knopf öffnet den Fächer auf Touchscreens.

Ebenen beim ScrollenNeu in 1.2

Dieselbe Karte dreht sich und geht auseinander, während die Sektion in die Fenstermitte scrollt, und bleibt so. Ohne JavaScript ruht sie aufgefächert.

Diorama-FensterNeu in 1.2

Eine Landschaft in vier Tiefenebenen hinter einem Fenster, gefärbt im Akzent. Die Szene neigt sich mit der Maus, die Ebenen verschieben sich gegeneinander, die Karte steht davor auf fester Fläche.

Element-InspektorNeu in 1.2

Ein Element, zur Seite gedreht und zerlegt, Schatten, Fläche, Rahmen, Inhalt und Fokusring. Gut für ein Designsystem oder eine Anleitung zu guten Components.

Details

Was Tiefenebenen mitbringt, aus Registry und Quellcode gelesen.

Kategorie
3D-Flächen
Bereich
Visuals
Variants
4
Version
1.0.0 · seit 1.2.0
Datei
components/ui/depth-layers.tsx
Bringt mit
utilskitsectionbuttoniconrevealscroll-scenedepth
npm-Pakete
Keine
Sprachen
Englisch und Deutsch, 3 eigene Texte
Studio
Im Code anpassen

Props

Die Schnittstelle DepthLayersProps aus components/ui/depth-layers.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch. Nimmt außerdem alle Props von Omit<React.HTMLAttributes<HTMLElement>, 'title'> an.

  • variantoptional
    DepthLayersVariant

    interfacescrolldioramainspector

  • eyebrowoptional
    string
  • titleoptional
    React.ReactNode
  • descriptionoptional
    string
  • layersoptional
    DepthLayer[]

    Names of the layers from the back to the front. interface and scroll use four, inspector five.

  • mediaoptional
    React.ReactNode

    interface, scroll: the picture on the back layer; diorama: replaces the drawn landscape planes (back to front)

  • cardoptional
    { title: string; text?: string; action?: string; meta?: string }

    Text on the card in the picture (interface, scroll, inspector) or on the diorama

  • actionoptional
    { label: string; href: string }

Eingebaute Texte

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

Alle 3 Texte anzeigen
openEnglisch Show the layersDeutsch Ebenen zeigen
closeEnglisch Put the layers togetherDeutsch Ebenen zusammenlegen
layersEnglisch LayersDeutsch Ebenen

Barrierefreiheit

Aus dem Quellcode der Component abgeleitet.

  • Setzt aria-label und aria-pressed, damit Screenreader Zweck und Zustand ansagen.
  • Zeigt bei Bedienung mit der Tastatur einen sichtbaren Fokusring.
  • Der Server rendert den Startzustand vollständig, JavaScript ergänzt nur die Interaktion.

Bewegung

  • Erscheint beim ersten Sichtkontakt mit einem kurzen Auftritt (kit-rise, useInView).
  • Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig. 3 Stellen im Code schalten Bewegung dann ab.

Quellcode

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

Datei anzeigen, 358 Zeilen
components/ui/depth-layers.tsx
'use client';

import { useCallback, useId, useRef, useState } from 'react';
import { cn, round, useTexts } from '@/lib/kit';
import { Eyebrow } from '@/components/ui/section';
import { Button } from '@/components/ui/button';
import { Icon, type IconName } from '@/components/ui/icon';
import { Reveal } from '@/components/ui/reveal';
import { useLive, useSceneProgress } from '@/components/ui/scroll-scene';
import { Glare, ease3d, usePointerTilt } from '@/components/ui/depth';

/*
 * A picture that comes apart into its layers. The layers sit flat on top of each other as one
 * finished picture and fan out into the room on hover, on focus, with a button, or while the
 * section scrolls by. Every layer keeps its name beside it in real text.
 *
 *   interface  a product card that fans out sideways into picture, panel, content and action
 *   scroll     the same fan, driven by the scroll: apart while the section passes the middle
 *   diorama    a drawn landscape in depth planes inside a window, leaning toward the pointer
 *   inspector  one element turned to the side, its shadow, surface, border, content and focus ring
 *
 * Without JavaScript the interface fan still opens on hover (CSS only). With reduced motion the
 * layers rest in their fanned position for interface, scroll and inspector, a still picture with
 * every name in view, and the diorama stands still.
 */

export type DepthLayersVariant = 'interface' | 'scroll' | 'diorama' | 'inspector';

export interface DepthLayer {
  title: string;
  text?: string;
  icon?: IconName;
}

export interface DepthLayersProps extends Omit<React.HTMLAttributes<HTMLElement>, 'title'> {
  variant?: DepthLayersVariant;
  eyebrow?: string;
  title?: React.ReactNode;
  description?: string;
  /** Names of the layers from the back to the front. interface and scroll use four, inspector five. */
  layers?: DepthLayer[];
  /** interface, scroll: the picture on the back layer; diorama: replaces the drawn landscape planes (back to front) */
  media?: React.ReactNode;
  /** Text on the card in the picture (interface, scroll, inspector) or on the diorama */
  card?: { title: string; text?: string; action?: string; meta?: string };
  action?: { label: string; href: string };
  labels?: Partial<(typeof TEXTS)['en']>;
}

const TEXTS = {
  en: { open: 'Show the layers', close: 'Put the layers together', layers: 'Layers' },
  de: { open: 'Ebenen zeigen', close: 'Ebenen zusammenlegen', layers: 'Ebenen' },
};

const FOCUS = 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-primary';

function Head({ eyebrow, title, description }: Pick<DepthLayersProps, 'eyebrow' | 'title' | 'description'>) {
  if (!title) return null;
  return (
    <Reveal>
      {eyebrow && <Eyebrow>{eyebrow}</Eyebrow>}
      <h2 className="text-balance text-h2 font-semibold text-foreground">{title}</h2>
      {description && <p className="mt-4 max-w-[34rem] text-pretty text-lead text-muted-foreground">{description}</p>}
    </Reveal>
  );
}

function Legend({ layers, focus, setFocus, label, reverse = true }: { layers: DepthLayer[]; focus: number; setFocus: (i: number) => void; label: string; reverse?: boolean }) {
  // Listed from the front to the back, the way the eye meets them
  const order = layers.map((l, i) => ({ l, i }));
  if (reverse) order.reverse();
  return (
    <ol aria-label={label} className="mt-8 grid gap-1" onPointerLeave={() => setFocus(-1)}>
      {order.map(({ l, i }) => (
        <li key={i}>
          <button
            type="button"
            aria-pressed={focus === i}
            onPointerEnter={() => setFocus(i)}
            onFocus={() => setFocus(i)}
            onBlur={() => setFocus(-1)}
            onClick={() => setFocus(focus === i ? -1 : i)}
            className={cn('flex min-h-11 w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-left transition-colors duration-300', FOCUS, focus === i ? 'bg-card shadow-[var(--highlight),var(--shadow-sm)]' : 'hover:bg-accent')}
          >
            <span className={cn('flex size-7 shrink-0 items-center justify-center rounded-md transition-colors', focus === i ? 'bg-primary text-primary-foreground' : 'bg-[color-mix(in_oklab,var(--foreground)_6%,transparent)] text-muted-foreground')}>
              <Icon name={l.icon ?? 'layers'} size={14} />
            </span>
            <span className="min-w-0">
              <span className="block text-[14.5px] font-semibold text-foreground">{l.title}</span>
              {l.text && <span className="block text-[13.5px] text-muted-foreground">{l.text}</span>}
            </span>
          </button>
        </li>
      ))}
    </ol>
  );
}

/* ------------------------------------------------------------------ */
/* The product card in four layers (interface, scroll)                 */
/* ------------------------------------------------------------------ */

function CardLayers({ media, card, focus, names, scrub }: { media?: React.ReactNode; card?: DepthLayersProps['card']; focus: number; names: string[]; scrub?: boolean }) {
  // Each layer reads --open (0 flat, 1 apart) from the stage
  const layer = (i: number, content: React.ReactNode, className?: string) => (
    <div
      className={cn('absolute inset-0 [transform-style:preserve-3d] motion-reduce:transition-none', scrub ? 'transition-opacity duration-500' : 'transition-[transform,opacity] duration-700', className)}
      style={{
        transform: `translateZ(calc(var(--open, 0) * ${i * 72}px + ${focus === i ? 30 : 0}px))`,
        opacity: focus >= 0 && focus !== i ? 0.35 : 1,
        transitionTimingFunction: ease3d,
      }}
    >
      {content}
      {/* The name of the layer beside it, only while apart */}
      <span
        aria-hidden
        className="absolute left-[calc(100%+14px)] flex items-center gap-2 whitespace-nowrap text-[12.5px] font-medium text-foreground max-sm:hidden"
        style={{ opacity: 'var(--open, 0)', top: ['6%', '44%', '58%', '84%'][i] ?? '6%' }}
      >
        <span className="h-px w-6 bg-border-strong" />
        {names[i]}
      </span>
    </div>
  );
  return (
    <>
      {layer(0, <div className="absolute inset-0 overflow-hidden rounded-2xl bg-surface shadow-[var(--shadow-lg)] [&>*]:size-full">{media}</div>)}
      {layer(1, <div className="absolute inset-x-[7%] bottom-[7%] top-[46%] rounded-xl border border-border bg-[color-mix(in_oklab,var(--card)_94%,transparent)] shadow-[var(--highlight),var(--shadow-md)]" />)}
      {layer(
        2,
        <div className="absolute inset-x-[7%] bottom-[7%] top-[46%] p-[6%]">
          {card?.meta && <p className="text-[11.5px] font-medium text-primary sm:text-[12.5px]">{card.meta}</p>}
          <p className="mt-1 text-[15px] font-semibold leading-tight text-foreground sm:text-[18px]">{card?.title}</p>
          {card?.text && <p className="mt-1.5 line-clamp-2 text-[12px] leading-snug text-muted-foreground sm:text-[13.5px]">{card.text}</p>}
        </div>,
      )}
      {layer(
        3,
        <div className="absolute bottom-[12%] right-[11%]">
          <span className="inline-flex h-8 items-center gap-1.5 rounded-lg bg-primary px-3 text-[12px] font-medium text-primary-foreground shadow-[var(--shadow-md)] sm:h-9 sm:px-3.5 sm:text-[13px]">
            {card?.action}
            <Icon name="arrow-right" size={13} />
          </span>
        </div>,
      )}
    </>
  );
}

/* ------------------------------------------------------------------ */
/* Diorama planes                                                      */
/* ------------------------------------------------------------------ */

const tone = (k: number) => `color-mix(in oklab, var(--primary) ${k}%, var(--background))`;
const PLANES = [
  { z: -260, d: '', fill: '' },
  { z: -180, d: 'M0 520L140 440L300 490L460 380L620 450L780 360L940 430L1100 350L1260 420L1400 380L1600 440V900H0Z', fill: tone(30) },
  { z: -100, d: 'M0 620C160 560 300 590 460 540S760 580 920 545S1220 500 1380 550S1520 580 1600 560V900H0Z', fill: tone(46) },
  { z: -30, d: 'M0 730C200 690 380 715 560 680S900 705 1100 675S1420 710 1600 690V900H0Z', fill: tone(64) },
];

function Diorama({ media, card, action }: Pick<DepthLayersProps, 'media' | 'card' | 'action'>) {
  const tilt = usePointerTilt<HTMLDivElement>({ ease: 0.09 });
  const gid = useId().replace(/:/g, '');
  const custom = Array.isArray(media) ? (media as React.ReactNode[]) : null;
  return (
    <div ref={tilt.ref} {...tilt.handlers} className="relative isolate aspect-[4/5] overflow-hidden rounded-3xl border border-border bg-background shadow-[var(--highlight),var(--shadow-lg)] [perspective:900px] sm:aspect-[16/10]">
      <div className="absolute inset-0 [transform-style:preserve-3d]" style={{ transform: 'rotateX(calc(var(--tilt-y, 0) * -5deg)) rotateY(calc(var(--tilt-x, 0) * 7deg))' }}>
        {PLANES.map((pl, i) => (
          <div key={i} aria-hidden data-clip-ok className="absolute inset-[-12%]" style={{ transform: `translateZ(${pl.z}px) scale(${round(1 + -pl.z / 900, 3)})` }}>
            {custom ? (
              <div className="size-full [&>*]:size-full">{custom[i % custom.length]}</div>
            ) : i === 0 ? (
              <svg viewBox="0 0 1600 900" preserveAspectRatio="xMidYMax slice" className="size-full">
                <defs>
                  <linearGradient id={`${gid}s`} x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0" stopColor={tone(6)} />
                    <stop offset="1" stopColor={tone(24)} />
                  </linearGradient>
                </defs>
                <rect width="1600" height="900" fill={`url(#${gid}s)`} />
                <circle cx="1010" cy="300" r="90" fill={tone(55)} />
              </svg>
            ) : (
              <svg viewBox="0 0 1600 900" preserveAspectRatio="xMidYMax slice" className="size-full">
                <path d={pl.d} fill={pl.fill} />
              </svg>
            )}
          </div>
        ))}
        {/* the card stands in front of every plane, on its own solid surface */}
        {card && (
          <div className="absolute inset-x-[6%] bottom-[7%] sm:inset-x-auto sm:left-[6%] sm:max-w-[24rem]" style={{ transform: 'translateZ(60px) scale(0.94)' }}>
            <div className="relative rounded-2xl border border-border bg-card p-5 shadow-[var(--highlight),var(--shadow-lg)] sm:p-6">
              {card.meta && <p className="text-[12.5px] font-medium text-primary">{card.meta}</p>}
              <p className="mt-1 text-[18px] font-semibold leading-tight text-foreground sm:text-[20px]">{card.title}</p>
              {card.text && <p className="mt-2 text-[14px] leading-relaxed text-muted-foreground">{card.text}</p>}
              {action && (
                <div className="mt-4">
                  <Button href={action.href} size="sm" icon="arrow-right">
                    {action.label}
                  </Button>
                </div>
              )}
              <Glare strength={0.22} />
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* Inspector: one element turned to the side                           */
/* ------------------------------------------------------------------ */

function Inspector({ card, focus, open, names }: { card?: DepthLayersProps['card']; focus: number; open: boolean; names: string[] }) {
  const layer = (i: number, content: React.ReactNode) => (
    <div
      className="absolute inset-0 [transform-style:preserve-3d] transition-[transform,opacity] duration-700 motion-reduce:transition-none"
      // The stack stands around its middle layer, so the turn swings it evenly to both sides and it never reaches over the copy
      style={{ transform: `translateZ(calc(${open ? i - 2 : round((i - 2) * 0.03, 2)} * var(--insp-gap) + ${focus === i ? 24 : 0}px))`, opacity: focus >= 0 && focus !== i ? 0.3 : 1, transitionTimingFunction: ease3d }}
    >
      {content}
      <span aria-hidden className={cn('absolute -top-7 left-0 whitespace-nowrap text-[12px] font-medium text-muted-foreground transition-opacity duration-500 max-sm:hidden', open ? 'opacity-100' : 'opacity-0')}>
        {names[i]}
      </span>
    </div>
  );
  return (
    <div className="relative mx-auto aspect-[1.5] w-[78%] [--insp-gap:46px] [transform-style:preserve-3d] sm:[--insp-gap:62px] lg:[--insp-gap:74px]">
      {layer(0, <div className="absolute inset-[-6%] rounded-[1.6rem] bg-[color-mix(in_oklab,var(--foreground)_14%,transparent)] blur-xl dark:bg-[color-mix(in_oklab,black_60%,transparent)]" />)}
      {layer(1, <div className="absolute inset-0 rounded-2xl bg-card" />)}
      {layer(2, <div className="absolute inset-0 rounded-2xl border-2 border-border-strong" />)}
      {layer(
        3,
        <div className="absolute inset-0 flex flex-col justify-between p-[8%]">
          <div>
            {card?.meta && <p className="text-[12px] font-medium text-primary">{card.meta}</p>}
            <p className="mt-1 text-[17px] font-semibold text-foreground sm:text-[19px]">{card?.title}</p>
            {card?.text && <p className="mt-1 text-[13px] text-muted-foreground">{card.text}</p>}
          </div>
          <span className="inline-flex h-9 w-fit items-center rounded-lg bg-primary px-4 text-[13px] font-medium text-primary-foreground">{card?.action}</span>
        </div>,
      )}
      {layer(4, <div className="absolute -inset-2 rounded-[1.15rem] border-2 border-primary" />)}
    </div>
  );
}

/* ------------------------------------------------------------------ */

const DEFAULT_LAYERS: Record<'en' | 'de', DepthLayer[]> = {
  en: [{ title: 'Picture' }, { title: 'Panel' }, { title: 'Content' }, { title: 'Action' }, { title: 'Focus ring' }],
  de: [{ title: 'Bild' }, { title: 'Fläche' }, { title: 'Inhalt' }, { title: 'Aktion' }, { title: 'Fokusring' }],
};

/** A picture that comes apart into its layers. */
export function DepthLayers({ variant = 'interface', eyebrow, title, description, layers, media, card, action, labels, className, ...rest }: DepthLayersProps) {
  const t = useTexts(TEXTS, labels);
  const de = t.open === TEXTS.de.open;
  const list = layers?.length ? layers : DEFAULT_LAYERS[de ? 'de' : 'en'];
  const names = list.map((l) => l.title);
  const [focus, setFocus] = useState(-1);
  const [open, setOpen] = useState(variant === 'inspector');
  const stageRef = useRef<HTMLDivElement | null>(null);

  // scroll: --open follows the progress, peaking while the section passes the middle of the window
  const live = useLive();
  const apply = useCallback((p: number) => {
    const el = stageRef.current;
    if (!el) return;
    const x = Math.min(1, Math.max(0, (p - 0.18) / 0.3));
    const v = x * x * (3 - 2 * x);
    el.style.setProperty('--open', String(round(v, 3)));
    el.style.setProperty('--turn', String(round(v, 3)));
  }, []);
  const scene = useSceneProgress<HTMLElement>(variant === 'scroll' && live, { mode: 'pass', self: false, onProgress: apply });

  const actionEl = action && (
    <div className="mt-8 max-sm:[&>span]:w-full">
      <Button href={action.href} icon="arrow-right" variant="secondary" className="max-sm:w-full">
        {action.label}
      </Button>
    </div>
  );

  if (variant === 'diorama') {
    return (
      <section {...rest} className={cn('relative py-20 sm:py-28', className)}>
        <div className="kit-container">
          {title && (
            <div className="mb-10 grid gap-6 sm:mb-14 md:grid-cols-[minmax(0,1fr)_minmax(0,0.8fr)] md:items-end">
              <Reveal>
                {eyebrow && <Eyebrow>{eyebrow}</Eyebrow>}
                <h2 className="text-balance text-h2 font-semibold text-foreground">{title}</h2>
              </Reveal>
              {description && <p className="text-pretty text-lead text-muted-foreground">{description}</p>}
            </div>
          )}
          <Diorama media={media} card={card} action={action} />
        </div>
      </section>
    );
  }

  const isInspector = variant === 'inspector';
  const turn = isInspector ? 'rotateX(8deg) rotateY(-42deg)' : 'rotateX(calc(var(--turn, 0) * 12deg)) rotateY(calc(var(--turn, 0) * -38deg))';

  return (
    <section {...rest} ref={scene} className={cn('relative overflow-x-clip py-20 sm:py-28', className)}>
      <div className="kit-container grid grid-cols-1 items-center gap-6 lg:grid-cols-[minmax(0,0.85fr)_minmax(0,1.15fr)] lg:gap-16">
        <div className="max-lg:contents">
          <div className="max-lg:order-1">
            <Head eyebrow={eyebrow} title={title} description={description} />
          </div>
          <div className="max-lg:order-4 [&>ol]:max-lg:mt-0">
            <Legend layers={list.slice(0, isInspector ? 5 : 4)} focus={focus} setFocus={setFocus} label={t.layers} />
          </div>
          {variant !== 'scroll' && (
            <button type="button" aria-pressed={open} onClick={() => setOpen((v) => !v)} className={cn('inline-flex min-h-11 w-fit cursor-pointer items-center gap-2 rounded-lg px-3 text-[14px] font-medium text-primary transition-colors hover:bg-accent max-lg:order-3 max-lg:justify-self-center lg:mt-4', FOCUS)}>
              <Icon name={open ? 'minimize' : 'layers'} size={16} />
              {open ? t.close : t.open}
            </button>
          )}
          <div className="max-lg:order-5">{actionEl}</div>
        </div>
        <div
          ref={stageRef}
          aria-hidden
          className={cn(
            'group/layers relative py-10 [perspective:1600px] max-lg:order-2 sm:py-14 lg:pr-28',
            // interface: CSS hover opens the fan even without JavaScript; the button and the legend open it as well
            variant === 'interface' && 'hover:[--open:1] hover:[--turn:1]',
            variant === 'interface' && (open || focus >= 0) && '[--open:1] [--turn:1]',
            // scroll without a running scene (no JavaScript, reduced motion) rests apart
            variant === 'scroll' && !live && '[--open:1] [--turn:1]',
          )}
        >
          {isInspector ? (
            <div className="[transform-style:preserve-3d]" style={{ transform: turn }}>
              <Inspector card={card} focus={focus} open={open} names={names} />
            </div>
          ) : (
            <div
              className={cn('relative mx-auto aspect-[4/5] w-[min(100%,22rem)] [transform-style:preserve-3d] motion-reduce:transition-none sm:w-[24rem]', !(variant === 'scroll' && live) && 'transition-transform duration-700')}
              style={{ transform: turn, transitionTimingFunction: ease3d }}
            >
              <CardLayers media={media} card={card} focus={focus} names={names} scrub={variant === 'scroll' && live} />
            </div>
          )}
        </div>
      </div>
    </section>
  );
}

Häufige Fragen

Wie installiere ich Tiefenebenen?

Mit aiw add @ai-website/depth-layers. Der Befehl legt die Datei components/ui/depth-layers.tsx in dein Projekt, der Code gehört danach dir. Mit dabei sind section, button, icon, reveal, scroll-scene und depth. Weitere npm-Pakete braucht sie nicht.

Welche Varianten hat Tiefenebenen?

4 Varianten, Oberfläche aufgefächert, Ebenen beim Scrollen, Diorama-Fenster und Element-Inspektor. Du wählst sie mit der Prop variant, zum Beispiel variant="scroll".

Kann ich die Texte von Tiefenebenen übersetzen?

Ja. Tiefenebenen bringt 3 eigene Texte auf Deutsch und Englisch mit, etwa „Ebenen zeigen“, „Ebenen zusammenlegen“ und „Ebenen“. Die Sprache folgt dem KitProvider, einzelne Texte ersetzt du über die Prop labels.

Kann ein KI-Assistent Tiefenebenen einbauen?

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

Oft gesucht

Direkt zu

In die Zwischenablage kopiert