İçeriğe geç
BigBang

Sık açılanlar

    ↑↓Gezin↵AçEscKapat
    EN

    Temeller

    Hareket

    Hareket her bileşenle birlikte tasarlanır. Kısa, amaçlı ve token'lardan gelir; azaltılmış harekette silinmez, yerini sakin bir geçişe bırakır.

    • Amaçlı: Her hareketin tek bir amacı vardır: geri bildirim, durum değişikliği, sistem durumu, mekânsal yön, süreklilik ya da küçük bir işarete dikkat çekmek. Yalnızca süs olan hareket ürün ekranında yer almaz.
    • Bileşenle birlikte: Basma, üzerine gelme, odak, yükleniyor, başarı, giriş ve çıkış hareketleri bileşenle aynı anda tasarlanır ve aynı hazır ayarlarla (preset) uygulanır. Düz, tepkisiz bir buton bitmiş sayılmaz.
    • Kısa: Etkileşim geri bildirimi 70–110 ms, geçişler 150–400 ms sürer. 400 ms üst sınırdır; kullanıcı hareketin bitmesini beklemez.
    • Önce üretken (productive): Arayüzün varsayılanı productive eğrilerdir. expressive eğri ve yaylar yalnızca marka anları içindir ve para akışlarında kullanılmaz.
    • Azaltılmış hareket değiştirir, silmez: “Hareketi azalt” açıkken ölçek ve kayma kalkar, kısa bir saydamlık geçişi kalır. Bilgi taşıyan hiçbir şey kaybolmaz.

    Öğe ne kadar büyükse ve ne kadar uzağa gidiyorsa süre o kadar uzun olur. Oynat düğmesi her süreyi aynı mesafede gösterir.

    TokenDeğerÖnizleme
    --bigbang-duration-instant

    Basılı durum, seçim işareti

    70 ms
    --bigbang-duration-fast

    Üzerine gelme, odak, renk değişimi, kısa çıkışlar

    110 ms
    --bigbang-duration-normal

    Açılır menü, akordeon içeriği, liste girişi

    150 ms
    --bigbang-duration-slow

    Diyalog, bildirim, başarı işareti

    240 ms
    --bigbang-duration-slower

    Grafik girişi, sayfa düzeyi geçiş (seyrek), üst sınır

    400 ms

    Her ailede üç eğri vardır. Standart eğri baştan sona görünen öğeler içindir (yeniden boyutlanan, yer değiştiren). Giriş (-enter) eğrisi hızlı başlar ve yumuşak durur; ekrana gelen öğeler içindir. Çıkış (-exit) eğrisi yavaş başlar ve hızlanarak ayrılır; ekrandan kalıcı olarak çıkan öğeler içindir. linear yalnızca dönen göstergeler ve ilerleme içindir.

    TokenDeğerÖnizleme
    --bigbang-easing-productive

    Varsayılan standart: basma, renk, sekme göstergesi

    cubic-bezier(0.2, 0, 0.38, 0.9)
    --bigbang-easing-expressive

    Marka anları; para akışlarında yok

    cubic-bezier(0.4, 0.14, 0.3, 1)
    --bigbang-easing-exit

    `easing-productive-exit` ile aynı; eski kullanımlar için

    cubic-bezier(0.2, 0, 1, 0.9)
    --bigbang-easing-productive-enter

    Menü, diyalog, bildirim, liste girişi

    cubic-bezier(0, 0, 0.38, 0.9)
    --bigbang-easing-productive-exit

    Menü, diyalog, bildirim çıkışı

    cubic-bezier(0.2, 0, 1, 0.9)
    --bigbang-easing-expressive-enter

    Marka anı girişi

    cubic-bezier(0, 0, 0.3, 1)
    --bigbang-easing-expressive-exit

    Marka anı çıkışı

    cubic-bezier(0.4, 0.14, 1, 1)
    --bigbang-easing-linear

    Dönen gösterge, iskelet parıltısı, belirsiz ilerleme

    cubic-bezier(0, 0, 1, 1)
    --bigbang-easing-spring-productive-fast

    Yay eğrisi: anahtar düğmesi, basma sonrası geri dönüş

    linear(0, 0.034, 0.114, 0.214, 0.319, 0.421, 0.514, 0.597, 0.669, 0.73, 0.781, 0.824, 0.859, 0.887, 0.91, 0.929, 0.944, 0.956, 0.965, 0.973, 0.979, 0.983, 0.987, 0.99, 1)
    --bigbang-easing-spring-productive-base

    Yay eğrisi: sekme göstergesi, liste yer değişimi, bildirim yığını

    linear(0, 0.034, 0.114, 0.215, 0.32, 0.422, 0.515, 0.598, 0.669, 0.73, 0.781, 0.823, 0.858, 0.886, 0.909, 0.928, 0.943, 0.955, 0.964, 0.972, 0.978, 0.983, 0.986, 0.989, 1)
    --bigbang-easing-spring-productive-slow

    Yay eğrisi: çekmece (drawer) girişi

    linear(0, 0.034, 0.114, 0.214, 0.319, 0.421, 0.513, 0.596, 0.667, 0.728, 0.779, 0.821, 0.856, 0.884, 0.907, 0.926, 0.941, 0.953, 0.963, 0.971, 0.977, 0.982, 0.986, 0.989, 1)
    --bigbang-easing-spring-expressive-base

    Yay eğrisi: marka anı, hafif taşma

    linear(0, 0.031, 0.108, 0.21, 0.322, 0.435, 0.542, 0.638, 0.722, 0.793, 0.851, 0.898, 0.934, 0.962, 0.982, 0.997, 1.006, 1.012, 1.015, 1.016, 1.016, 1.015, 1.013, 1.012, 1)

    Yay (spring), süre yerine fiziksel değerlerle tanımlanır: sertlik (stiffness), sönüm (damping) ve kütle (mass). Değerler DTCG $extensions alanında durur; derleme her yay için oturma süresini (hedefin %1 yakınına geldiği an) doğrular ve buna uyan bir CSS linear() eğrisi üretir. CSS’te süre ile eğri birlikte kullanılır: --bigbang-spring-productive-fast ve --bigbang-easing-spring-productive-fast. JavaScript motorları (Motion, React Native Reanimated) aynı sayıları motionValues.spring üzerinden alır.

    TokenAçık modKoyu mod
    --bigbang-spring-productive-fast

    Sertlik 900, sönüm 60 · taşma yok

    0.23s0.23s0.23s0.23s0.23s0.23s
    --bigbang-spring-productive-base

    Sertlik 500, sönüm 45 · taşma yok

    0.31s0.31s0.31s0.31s0.31s0.31s
    --bigbang-spring-productive-slow

    Sertlik 300, sönüm 35 · taşma yok

    0.4s0.4s0.4s0.4s0.4s0.4s
    --bigbang-spring-expressive-base

    Sertlik 380, sönüm 31 · hafif taşma, yalnızca marka anları

    0.33s0.33s0.33s0.33s0.33s0.33s

    Ne zaman yay, ne zaman Bézier? Kullanıcının elinden çıkan ya da kesilebilen hareketlerde yay kullanın: basmadan sonra geri dönüş, anahtar düğmesi, sekme göstergesi, sıralama, çekmece. Hareket ortasında hedef değişirse yay doğal görünür. Saydamlık, renk ve sabit süresi olan giriş/çıkışlarda Bézier eğri kullanın. Ürün ekranındaki yaylar kritik sönümlüdür (taşma yapmaz).

    Bileşenler süre ve eğriyi doğrudan değil, kalıba göre adlanmış token’lardan alır: --bigbang-motion-<kalıp>-<evre>-<özellik>. Örneğin motion-popover-enter-duration, motion-dialog-scale, motion-list-stagger. Bir kalıbın hızı değişecekse yalnızca bu token değişir.

    TokenAçık modKoyu mod
    --bigbang-motion-press-duration0.07s0.07s0.07s0.07s0.07s0.07s
    --bigbang-motion-press-easingcubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)
    --bigbang-motion-press-scale0.970.970.970.970.970.97
    --bigbang-motion-release-duration0.23s0.23s0.23s0.23s0.23s0.23s
    --bigbang-motion-glass-press-scale1.021.021.021.021.021.02
    --bigbang-motion-hover-duration0.11s0.11s0.11s0.11s0.11s0.11s
    --bigbang-motion-hover-easingcubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)
    --bigbang-motion-hover-lift-2px-2px-2px-2px-2px-2px
    --bigbang-motion-focus-duration0.11s0.11s0.11s0.11s0.11s0.11s
    --bigbang-motion-focus-easingcubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)cubic-bezier(0.2, 0, 0.38, 0.9)
    --bigbang-motion-feedback-duration0.24s0.24s0.24s0.24s0.24s0.24s
    --bigbang-motion-feedback-easingcubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)
    --bigbang-motion-feedback-scale0.60.60.60.60.60.6
    --bigbang-motion-feedback-hold2s2s2s2s2s2s
    --bigbang-motion-selection-duration0.11s0.11s0.11s0.11s0.11s0.11s
    --bigbang-motion-selection-easingcubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)
    --bigbang-motion-selection-scale0.60.60.60.60.60.6
    --bigbang-motion-switch-duration0.23s0.23s0.23s0.23s0.23s0.23s
    --bigbang-motion-popover-enter-duration0.15s0.15s0.15s0.15s0.15s0.15s
    --bigbang-motion-popover-exit-duration0.11s0.11s0.11s0.11s0.11s0.11s
    --bigbang-motion-popover-enter-easingcubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)
    --bigbang-motion-popover-exit-easingcubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)
    --bigbang-motion-popover-scale0.960.960.960.960.960.96
    --bigbang-motion-popover-distance4px4px4px4px4px4px
    --bigbang-motion-tooltip-delay0.4s0.4s0.4s0.4s0.4s0.4s
    --bigbang-motion-tooltip-enter-duration0.11s0.11s0.11s0.11s0.11s0.11s
    --bigbang-motion-tooltip-exit-duration0.07s0.07s0.07s0.07s0.07s0.07s
    --bigbang-motion-tooltip-distance2px2px2px2px2px2px
    --bigbang-motion-dialog-enter-duration0.24s0.24s0.24s0.24s0.24s0.24s
    --bigbang-motion-dialog-exit-duration0.15s0.15s0.15s0.15s0.15s0.15s
    --bigbang-motion-dialog-enter-easingcubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)
    --bigbang-motion-dialog-exit-easingcubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)
    --bigbang-motion-dialog-scale0.960.960.960.960.960.96
    --bigbang-motion-dialog-distance8px8px8px8px8px8px
    --bigbang-motion-drawer-enter-duration0.4s0.4s0.4s0.4s0.4s0.4s
    --bigbang-motion-drawer-exit-duration0.24s0.24s0.24s0.24s0.24s0.24s
    --bigbang-motion-drawer-exit-easingcubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)
    --bigbang-motion-drawer-distance100%100%100%100%100%100%
    --bigbang-motion-toast-enter-duration0.24s0.24s0.24s0.24s0.24s0.24s
    --bigbang-motion-toast-exit-duration0.15s0.15s0.15s0.15s0.15s0.15s
    --bigbang-motion-toast-enter-easingcubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)
    --bigbang-motion-toast-exit-easingcubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)cubic-bezier(0.2, 0, 1, 0.9)
    --bigbang-motion-toast-distance8px8px8px8px8px8px
    --bigbang-motion-collapsible-duration0.15s0.15s0.15s0.15s0.15s0.15s
    --bigbang-motion-collapsible-easingcubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)cubic-bezier(0, 0, 0.38, 0.9)
    --bigbang-motion-collapsible-distance4px4px4px4px4px4px
    --bigbang-motion-collapsible-icon-duration0.15s0.15s0.15s0.15s0.15s0.15s
    --bigbang-motion-indicator-duration0.31s0.31s0.31s0.31s0.31s0.31s
    --bigbang-motion-skeleton-reveal-duration0.11s0.11s0.11s0.11s0.11s0.11s
    --bigbang-motion-route-enter-duration0.15s0.15s0.15s0.15s0.15s0.15s
    --bigbang-motion-route-exit-duration0.11s0.11s0.11s0.11s0.11s0.11s
    --bigbang-motion-route-shared-duration0.24s0.24s0.24s0.24s0.24s0.24s
    --bigbang-motion-list-enter-duration0.15s0.15s0.15s0.15s0.15s0.15s
    --bigbang-motion-list-exit-duration0.11s0.11s0.11s0.11s0.11s0.11s
    --bigbang-motion-list-move-duration0.31s0.31s0.31s0.31s0.31s0.31s
    --bigbang-motion-list-distance4px4px4px4px4px4px
    --bigbang-motion-list-stagger0.03s0.03s0.03s0.03s0.03s0.03s
    --bigbang-motion-value-duration0.11s0.11s0.11s0.11s0.11s0.11s
    --bigbang-motion-progress-duration0.15s0.15s0.15s0.15s0.15s0.15s
    --bigbang-motion-chart-enter-duration0.4s0.4s0.4s0.4s0.4s0.4s
    --bigbang-motion-chart-update-duration0.24s0.24s0.24s0.24s0.24s0.24s

    Kendi üzerine gelme rengi olmayan yüzeylerde (kart, liste satırı, ikon buton) etkileşim, metin renginde ince bir katmanla gösterilir. Opaklıklar:

    TokenAçık modKoyu mod
    --bigbang-state-layer-hovered

    Üzerine gelindiğinde

    0.080.080.080.080.080.08
    --bigbang-state-layer-focused

    Klavye odağında (halka ile birlikte)

    0.10.10.10.10.10.1
    --bigbang-state-layer-pressed

    Basılıyken

    0.10.10.10.10.10.1
    --bigbang-state-layer-dragged

    Sürüklenirken

    0.160.160.160.160.160.16

    Aşağıdaki örnekler @bigbang/ui paketindeki motion hazır ayarlarını kullanır. Bileşenler aynı sınıfları kendi stil tariflerinde kullanır; prop değişmez.

    Butonlar basılıyken 70 ms’de %97’ye küçülür ve bırakınca hızlı yayla döner. İkon butonlar ve kartlar durum katmanı kullanır; tıklanabilir kart üzerine gelindiğinde 2 px yükselir.

    import { motion } from '@bigbang/ui';
    import { primaryButton, secondaryButton } from '../demo';
    export default function Press() {
    return (
    <div className="flex flex-wrap items-center gap-lg">
    <button type="button" className={primaryButton}>
    Onayla
    </button>
    <button type="button" className={secondaryButton}>
    Vazgeç
    </button>
    <button
    type="button"
    aria-label="Diğer seçenekler"
    className={`${motion.press} ${motion.stateLayer} inline-flex size-(--bigbang-button-height-md) items-center justify-center rounded-full text-fg-default outline-none focus-visible:focus-outline`}
    >
    <svg aria-hidden="true" viewBox="0 0 16 16" fill="currentColor" className="size-4">
    <circle cx="3" cy="8" r="1.5" />
    <circle cx="8" cy="8" r="1.5" />
    <circle cx="13" cy="8" r="1.5" />
    </svg>
    </button>
    <button
    type="button"
    className={`${motion.stateLayer} ${motion.lift} flex w-56 flex-col items-start gap-xs rounded-lg border border-line-subtle bg-surface-raised p-lg text-start outline-none focus-visible:focus-outline`}
    >
    <span className="type-label-md text-fg-default">Kart başlığı</span>
    <span className="type-body-sm text-fg-subtle">Kart açıklaması</span>
    </button>
    </div>
    );
    }

    İşlem sürerken etiket yerinde kalır (genişlik değişmez), üstünde dönen gösterge belirir. Başarı işareti %60’tan büyüyerek gelir, iki saniye kalır ve tekrar etmez. Sonuç ekran okuyucuya ayrıca duyurulur.

    import { motion, motionValues } from '@bigbang/ui';
    import { useEffect, useState } from 'react';
    import { primaryButton } from '../demo';
    type Step = 'idle' | 'busy' | 'done';
    export default function Feedback() {
    const [step, setStep] = useState<Step>('idle');
    useEffect(() => {
    // The first timer stands in for the request; the second returns the button to rest.
    const next = step === 'busy' ? 'done' : step === 'done' ? 'idle' : null;
    if (!next) return undefined;
    const id = setTimeout(() => setStep(next), step === 'busy' ? 1200 : motionValues.motion['feedback-hold']);
    return () => clearTimeout(id);
    }, [step]);
    return (
    <div className="flex items-center gap-lg">
    <button
    type="button"
    aria-disabled={step !== 'idle'}
    aria-busy={step === 'busy'}
    onClick={() => step === 'idle' && setStep('busy')}
    className={`${primaryButton} relative min-w-40`}
    >
    <span className={step === 'idle' ? '' : 'opacity-0'}>Kaydet</span>
    {step === 'busy' ? (
    <span className="absolute inset-0 flex items-center justify-center">
    <svg aria-hidden="true" viewBox="0 0 16 16" fill="none" className="size-4 animate-spin reduce-motion:animate-none">
    <circle cx="8" cy="8" r="6.5" stroke="currentColor" strokeOpacity="0.3" strokeWidth="2" />
    <path d="M14.5 8A6.5 6.5 0 0 0 8 1.5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
    </svg>
    </span>
    ) : null}
    {step === 'done' ? (
    <span className={`${motion.success} absolute inset-0 flex items-center justify-center gap-xs`}>
    <svg aria-hidden="true" viewBox="0 0 16 16" fill="none" className="size-4">
    <path d="M3 8.5l3 3 7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
    Kaydedildi
    </span>
    ) : null}
    </button>
    <span role="status" className="sr-only">
    {step === 'done' ? 'Kaydedildi' : ''}
    </span>
    </div>
    );
    }

    Kenarlık rengi 110 ms’de değişir; odak halkası gecikmesiz görünür. Hata mesajı saydamlaşarak 4 px aşağı iner. Alan sallanmaz.

    import { motion } from '@bigbang/ui';
    import { useId, useState } from 'react';
    import { secondaryButton } from '../demo';
    export default function Field() {
    const id = useId();
    const [value, setValue] = useState('');
    const [invalid, setInvalid] = useState(false);
    return (
    <form
    noValidate
    onSubmit={(event) => {
    event.preventDefault();
    setInvalid(value.trim() === '');
    }}
    className="flex w-full max-w-sm flex-col items-start gap-sm"
    >
    <label htmlFor={id} className="type-label-md text-fg-default">
    Alan etiketi
    </label>
    <input
    id={id}
    value={value}
    onChange={(event) => setValue(event.target.value)}
    aria-invalid={invalid || undefined}
    aria-describedby={invalid ? `${id}-error` : undefined}
    className={`${motion.field} h-(--bigbang-input-height-md) w-full rounded-(--bigbang-input-radius) border border-line-input bg-surface-raised px-(--bigbang-input-padding-inline) type-body-md text-fg-default outline-none focus-visible:border-line-focus focus-visible:focus-outline-inset aria-invalid:border-line-danger`}
    />
    {invalid ? (
    <p id={`${id}-error`} className={`${motion.fieldMessage} type-body-sm text-fg-danger`}>
    Hata mesajı burada yer alır.
    </p>
    ) : null}
    <button type="submit" className={secondaryButton}>
    Kontrol et
    </button>
    </form>
    );
    }

    Onay işareti %60’tan büyüyerek gelir. Anahtar düğmesi hızlı yayla kayar; azaltılmış harekette yer değiştirmesi anlık olur, renk geçişi kalır.

    import { motion } from '@bigbang/ui';
    import { useState } from 'react';
    const row =
    'flex items-center gap-md type-body-md text-fg-default outline-none focus-visible:focus-outline rounded-xs';
    const state = (on: boolean) => (on ? { 'data-checked': '' } : { 'data-unchecked': '' });
    export default function Selection() {
    const [checked, setChecked] = useState(true);
    const [on, setOn] = useState(false);
    return (
    <div className="flex flex-col items-start gap-lg">
    <button type="button" role="checkbox" aria-checked={checked} onClick={() => setChecked(!checked)} className={row}>
    <span
    className={`${motion.press} ${motion.hover} flex size-5 items-center justify-center rounded-xs border ${checked ? 'border-transparent bg-action-primary-default text-action-on-primary-default' : 'border-line-input bg-surface-raised'}`}
    >
    <svg aria-hidden="true" viewBox="0 0 16 16" fill="none" {...state(checked)} className={`${motion.selectionMark} size-3.5`}>
    <path d="M3 8.5l3 3 7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
    </span>
    Seçenek etiketi
    </button>
    <button type="button" role="switch" aria-checked={on} onClick={() => setOn(!on)} className={row}>
    <span
    className={`${motion.hover} relative h-6 w-10 rounded-full [--switch-travel:16px] ${on ? 'bg-action-primary-default' : 'bg-line-input'}`}
    >
    <span {...state(on)} className={`${motion.switchThumb} absolute start-0.5 top-0.5 size-5 rounded-full bg-surface-raised shadow-raised`} />
    </span>
    Örnek ayar
    </button>
    </div>
    );
    }

    Menü bağlı olduğu öğeden %96 ölçek ve 4 px ile açılır (150 ms), daha hızlı kapanır (110 ms). Diyalog 8 px yükselerek 240 ms’de gelir; perde aynı hızda koyulaşır. İpucu 400 ms bekledikten sonra 110 ms’de görünür.

    import { motion } from '@bigbang/ui';
    import { useId, useState } from 'react';
    import { panel, primaryButton, secondaryButton, stage } from '../demo';
    // Demo only: shows the motion. Real overlays come from Base UI parts, which also manage focus and Escape.
    const visibility = (open: boolean) => (open ? { 'data-open': '' } : { 'data-closed': '', inert: true });
    export default function Overlays() {
    const id = useId();
    const [menu, setMenu] = useState(false);
    const [dialog, setDialog] = useState(false);
    return (
    <div className={`${stage} min-h-64 p-xl`}>
    <div className="flex gap-lg">
    <div className="relative">
    <button type="button" aria-expanded={menu} aria-controls={`${id}-menu`} onClick={() => setMenu(!menu)} className={secondaryButton}>
    Eylemler
    </button>
    <div
    id={`${id}-menu`}
    {...visibility(menu)}
    className={`${motion.popover} absolute start-0 top-full z-10 mt-xs flex w-48 flex-col rounded-md border border-line-subtle bg-surface-raised p-xs shadow-overlay`}
    >
    {['İndir', 'Paylaş', 'Arşivle'].map((item) => (
    <button key={item} type="button" onClick={() => setMenu(false)} className={`${motion.hover} rounded-sm px-md py-sm text-start type-body-md text-fg-default hover:bg-surface-hovered`}>
    {item}
    </button>
    ))}
    </div>
    </div>
    <button type="button" onClick={() => setDialog(true)} className={primaryButton}>
    Diyaloğu aç
    </button>
    </div>
    <div {...visibility(dialog)} onClick={() => setDialog(false)} className={`${motion.scrim} absolute inset-0 z-20 rounded-md bg-surface-scrim`} />
    <div
    role="dialog"
    aria-labelledby={`${id}-title`}
    {...visibility(dialog)}
    className={`${motion.dialog} ${panel} absolute inset-x-xl top-xl z-30 flex flex-col items-start gap-md`}
    >
    <h3 id={`${id}-title`} className="m-0 type-heading-sm">
    Diyalog başlığı
    </h3>
    <p className="m-0 type-body-md text-fg-subtle">Diyalog açıklaması burada yer alır.</p>
    <button type="button" onClick={() => setDialog(false)} className={secondaryButton}>
    Kapat
    </button>
    </div>
    </div>
    );
    }

    Bildirim 8 px yükselerek gelir, yığın orta yayla kayar. Kendiliğinden kapanan bildirimler en az 5 saniye kalır ve üzerine gelindiğinde ya da odaklanıldığında bekler.

      import { motion, motionValues } from '@bigbang/ui';
      import { useState } from 'react';
      import { primaryButton, stage } from '../demo';
      interface Item {
      id: number;
      leaving?: boolean;
      }
      export default function Toast() {
      const [items, setItems] = useState<Item[]>([]);
      const dismiss = (id: number) => {
      setItems((current) => current.map((item) => (item.id === id ? { ...item, leaving: true } : item)));
      setTimeout(() => setItems((current) => current.filter((item) => item.id !== id)), motionValues.motion['toast-exit-duration']);
      };
      return (
      <div className={`${stage} min-h-56`}>
      <button type="button" onClick={() => setItems((current) => [...current.slice(-2), { id: Date.now() }])} className={primaryButton}>
      Bildirim göster
      </button>
      <ol aria-live="polite" className="absolute end-0 bottom-0 m-0 flex w-72 list-none flex-col gap-sm p-0">
      {items.map((item) => (
      <li
      key={item.id}
      {...(item.leaving ? { 'data-ending-style': '' } : {})}
      className={`${motion.toast} flex items-center justify-between gap-md rounded-md bg-surface-inverse px-lg py-md type-body-md text-fg-inverse shadow-overlay`}
      >
      Bildirim metni
      <button type="button" onClick={() => dismiss(item.id)} className={`${motion.press} rounded-sm px-sm type-label-sm text-fg-inverse underline`}>
      Kapat
      </button>
      </li>
      ))}
      </ol>
      </div>
      );
      }

      Gösterge etkin sekmeyi orta yayla izler. Akordeon panelinin yüksekliği anında değişir; içerik saydamlaşarak 4 px iner, ok yarım tur döner. Yükseklik canlandırılmaz, çünkü yerleşim (layout) canlandırmak ana iş parçacığını yorar.

      import { motion } from '@bigbang/ui';
      import { type CSSProperties, useId, useLayoutEffect, useRef, useState } from 'react';
      export default function Disclosure() {
      const tabs = ['Birinci sekme', 'İkinci sekme', 'Üçüncü sekme'];
      const id = useId();
      const [active, setActive] = useState(0);
      const [open, setOpen] = useState(false);
      const [bar, setBar] = useState({ left: 0, width: 0 });
      const listRef = useRef<HTMLDivElement>(null);
      useLayoutEffect(() => {
      const tab = listRef.current?.children[active] as HTMLElement | undefined;
      if (tab) setBar({ left: tab.offsetLeft, width: tab.offsetWidth });
      }, [active]);
      return (
      <div className="flex w-full max-w-md flex-col gap-xl">
      <div className="relative border-b border-line-subtle">
      <div ref={listRef} role="tablist" aria-label="Bölümler" className="flex">
      {tabs.map((tab, index) => (
      <button
      key={tab}
      type="button"
      role="tab"
      aria-selected={index === active}
      onClick={() => setActive(index)}
      className={`${motion.hover} px-lg py-sm type-label-md outline-none focus-visible:focus-outline-inset ${index === active ? 'text-fg-default' : 'text-fg-subtle hover:text-fg-default'}`}
      >
      {tab}
      </button>
      ))}
      </div>
      <span
      aria-hidden="true"
      style={{ '--active-tab-left': `${bar.left}px`, '--active-tab-width': `${bar.width}px` } as CSSProperties}
      className={`${motion.indicator} start-0 -bottom-px h-0.5 rounded-full bg-action-primary-default`}
      />
      </div>
      <div className="group flex flex-col" {...(open ? { 'data-open': '' } : {})}>
      <button
      type="button"
      aria-expanded={open}
      aria-controls={`${id}-panel`}
      onClick={() => setOpen(!open)}
      className={`${motion.hover} flex items-center justify-between rounded-sm py-sm type-label-md text-fg-default outline-none focus-visible:focus-outline`}
      >
      Ayrıntılar
      <svg aria-hidden="true" viewBox="0 0 16 16" fill="none" className={`${motion.collapsibleIcon} size-4`}>
      <path d="M4 6l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
      </svg>
      </button>
      {open ? (
      <p id={`${id}-panel`} className={`${motion.collapsible} m-0 type-body-md text-fg-subtle`}>
      Açılan içerik burada yer alır.
      </p>
      ) : null}
      </div>
      </div>
      );
      }

      İskelet parıltısı üç kez geçer (4,5 sn) ve durur. İçerik geldiğinde 110 ms’de belirir. Azaltılmış harekette iskelet durağandır.

      import { motion } from '@bigbang/ui';
      import { useEffect, useState } from 'react';
      import { secondaryButton } from '../demo';
      export default function Skeleton() {
      const [loading, setLoading] = useState(true);
      useEffect(() => {
      if (!loading) return undefined;
      // Stands in for the request.
      const id = setTimeout(() => setLoading(false), 1800);
      return () => clearTimeout(id);
      }, [loading]);
      return (
      <div className="flex w-full max-w-md flex-col items-start gap-lg">
      <button type="button" onClick={() => setLoading(true)} className={secondaryButton}>
      Yenile
      </button>
      <div aria-busy={loading} className="flex w-full flex-col gap-sm rounded-lg border border-line-subtle bg-surface-raised p-lg">
      {loading ? (
      <>
      <span className={`${motion.skeleton} block h-5 w-1/2 rounded-sm`} />
      <span className={`${motion.skeleton} block h-4 w-full rounded-sm`} />
      <span className={`${motion.skeleton} block h-4 w-3/4 rounded-sm`} />
      </>
      ) : (
      <div className={`${motion.reveal} flex flex-col gap-sm`}>
      <span className="type-heading-sm text-fg-default">Kart başlığı</span>
      <span className="type-body-md text-fg-subtle">Kart açıklaması burada yer alır.</span>
      </div>
      )}
      </div>
      </div>
      );
      }

      Yeni öğe 4 px kayarak 150 ms’de gelir; çıkan öğe 110 ms’de solar. Kademeli girişte öğeler arası 30 ms beklenir; sıra 8. öğeden sonra beklemez, böylece bir grup 400 ms’yi geçmez.

      • Birinci öğe
      • İkinci öğe
      • Üçüncü öğe
      import { motion, motionValues } from '@bigbang/ui';
      import { type CSSProperties, useState } from 'react';
      import { secondaryButton } from '../demo';
      interface Row {
      id: number;
      label: string;
      leaving?: boolean;
      }
      export default function List() {
      const [rows, setRows] = useState<Row[]>(() => ['Birinci öğe', 'İkinci öğe', 'Üçüncü öğe'].map((label, id) => ({ id, label })));
      const [round, setRound] = useState(0);
      const add = () => setRows((current) => [{ id: Date.now(), label: 'Yeni öğe' }, ...current]);
      const remove = (id: number) => {
      // Keep the row while it fades out, then drop it.
      setRows((current) => current.map((row) => (row.id === id ? { ...row, leaving: true } : row)));
      setTimeout(() => setRows((current) => current.filter((row) => row.id !== id)), motionValues.motion['list-exit-duration']);
      };
      return (
      <div className="flex w-full max-w-md flex-col gap-md">
      <div className="flex gap-md">
      <button type="button" onClick={add} className={secondaryButton}>
      Ekle
      </button>
      <button type="button" onClick={() => setRound(round + 1)} className={secondaryButton}>
      Yeniden oynat
      </button>
      </div>
      <ul key={round} className="m-0 flex list-none flex-col gap-xs p-0">
      {rows.map((row, index) => (
      <li
      key={row.id}
      {...(row.leaving ? { 'data-ending-style': '' } : {})}
      style={{ '--stagger-index': index } as CSSProperties}
      className={`${motion.listItem} flex items-center justify-between rounded-md border border-line-subtle bg-surface-raised px-lg py-sm type-body-md text-fg-default`}
      >
      {row.label}
      <button type="button" onClick={() => remove(row.id)} className={`${motion.hover} rounded-sm px-sm type-label-sm text-fg-subtle hover:bg-surface-hovered`}>
      Kaldır
      </button>
      </li>
      ))}
      </ul>
      </div>
      );
      }

      Değişen tutar 110 ms’de yer değiştirerek belirir. Tutarlar sayarak artmaz ya da yuvarlanmaz: ara değerler yanlış bir tutar gösterir. İlerleme çubuğunun dolgusu translate ile kayar, böylece uçları her değerde yuvarlak kalır; ölçer ve diğer dolgular scale-x ile büyür.

      Değer24
      İlerleme
      import { motion } from '@bigbang/ui';
      import { type CSSProperties, useState } from 'react';
      import { secondaryButton } from '../demo';
      const limit = 100;
      export default function Value() {
      const number = new Intl.NumberFormat('tr-TR');
      const [total, setTotal] = useState(24);
      return (
      <div className="flex w-full max-w-md flex-col items-start gap-lg">
      <div className="flex flex-col gap-xs">
      <span className="type-body-sm text-fg-subtle">Değer</span>
      {/* A new key remounts the figure, so it cross-fades. Values never count up or roll. */}
      <span key={total} className={`${motion.reveal} type-heading-lg tabular-nums text-fg-default`}>
      {number.format(total)}
      </span>
      </div>
      <div className="flex w-full flex-col gap-xs">
      <div
      role="progressbar"
      aria-label="İlerleme"
      aria-valuemin={0}
      aria-valuemax={limit}
      aria-valuenow={Math.min(total, limit)}
      className="h-2 w-full overflow-hidden rounded-full bg-surface-sunken"
      >
      <div style={{ '--progress': Math.min(total / limit, 1) } as CSSProperties} className={`${motion.progress} h-full w-full rounded-full bg-action-primary-default`} />
      </div>
      <span className="type-body-sm text-fg-subtle">İlerleme</span>
      </div>
      <button type="button" onClick={() => setTotal(Math.min(total + 8, limit))} className={secondaryButton}>
      Değeri artır
      </button>
      </div>
      );
      }

      Sayfa değişiminde yalnızca içerik alanı değişir: eski içerik 110 ms’de solar, yenisi 150 ms’de belirir; kabuk (başlık, yan menü) yerinde kalır. Her sayfa geçişinde “her şey yukarı kayarak gelir” yapılmaz. Liste satırından ayrıntı sayfasına geçişte satırın başlığı ortak öğe olarak 240 ms’de yerine gider (View Transitions API). Desteklemeyen tarayıcıda geçiş anlık olur.

      Grafik ilk açılışta 400 ms’de çizilir, veri güncellenince 240 ms’de geçer. Filtre ya da sayfalama sonrasında grafik baştan çizilmez. ECharts’ta animationDuration ve animationDurationUpdate değerleri motionValues.motion üzerinden alınır; azaltılmış harekette animation: false olur.

      • Bir ekranda en fazla 3 kademe grubu olur, grup içinde öğeler arası en fazla 40 ms (varsayılan 30 ms) beklenir, toplam 400 ms’yi geçmez.
      • Sıra: önce kap (panel, diyalog), sonra içerik. İçerik kabı beklemez; aynı anda başlar, yalnızca gecikmeyle ayrışır.
      • Giriş hareketi yalnızca içeriğin ilk görünüşündedir; yenileme, filtre ve sayfalamada tekrar etmez.
      • Aynı anda en fazla bir büyük öğe hareket eder (diyalog, çekmece ya da sayfa içeriği).

      İşletim sistemindeki “hareketi azalt” ayarı ya da ürün içindeki data-motion="reduce" geçersiz kılması token’ları değiştirir: ölçekler 1, mesafeler 0 olur, uzun soluklar 110 ms’ye iner. Bileşen kodunda ayrı bir dal gerekmez. Döngüler (skeleton, indeterminate) reduce-motion: varyantıyla durur.

      Öğe Tam hareket Azaltılmış harekette
      Basma %97 ölçek, yay ile dönüş Yalnız renk değişimi
      Kart üzerine gelme 2 px yükselme Yalnız kenarlık rengi
      Başarı işareti %60’tan büyüme, 240 ms 110 ms belirme, durağan
      Menü, ipucu Ölçek + kayma 110 ms belirme
      Diyalog, çekmece Ölçek + kayma / tam genişlik kayma 110 ms belirme
      Bildirim, liste 8 / 4 px kayma, kademe Belirme, kademe yok
      Anahtar, sekme göstergesi Yay ile kayma Anlık yer değişimi, renk geçişi kalır
      İskelet, belirsiz ilerleme Parıltı / kayan çubuk Durağan blok / tam çubuk
      Dönen gösterge Döner Durağan simge ve metin
      Grafik Çizilerek gelir Anında
      Marka animasyonu (Rive) Bir kez oynar Son kare durağan gösterilir
      Hareket azaltıldığında bu panel yalnızca belirir; hiçbir şey büyümez ya da kaymaz.
      import { motion } from '@bigbang/ui';
      import { useState } from 'react';
      import { panel, primaryButton, secondaryButton } from '../demo';
      const visibility = (open: boolean) => (open ? { 'data-open': '' } : { 'data-closed': '', inert: true });
      export default function Reduced() {
      const [reduce, setReduce] = useState(true);
      const [open, setOpen] = useState(false);
      return (
      // data-motion="reduce" applies the same token replacements as the operating-system setting.
      <div {...(reduce ? { 'data-motion': 'reduce' } : {})} className="relative flex w-full flex-col items-start gap-lg">
      <label className="flex items-center gap-sm type-body-md text-fg-default">
      <input type="checkbox" checked={reduce} onChange={() => setReduce(!reduce)} className="size-4 accent-(--bigbang-color-action-primary-default)" />
      Hareketi azalt
      </label>
      <div className="flex gap-lg">
      <button type="button" onClick={() => setOpen(!open)} className={primaryButton}>
      {open ? 'Paneli gizle' : 'Paneli göster'}
      </button>
      <button type="button" className={secondaryButton}>
      Bana bas
      </button>
      </div>
      <div {...visibility(open)} className={`${motion.dialog} ${panel} w-full max-w-sm type-body-md`}>
      Hareket azaltıldığında bu panel yalnızca belirir; hiçbir şey büyümez ya da kaymaz.
      </div>
      </div>
      );
      }

      Ödeme, transfer, iade ve limit gibi S1/S2 akışlarında yalnızca productive eğri ve kritik sönümlü yaylar kullanılır. Bu akışlarda kutlama animasyonu, taşan yay, sallanma, sayan ya da yuvarlanan tutar yoktur. Onay ekranında başarı işareti bir kez gelir ve durağan kalır. Bir hata, alanın rengi, simgesi ve metniyle anlatılır.

      • Yalnızca opacity, transform (scale, translate, rotate) ve renk canlandırılır. Sekme göstergesinin genişliği tek istisnadır. width, height, top, left, margin canlandırılmaz.
      • will-change yalnızca hareket süresince eklenir. Görünmeyen canvas animasyonu durur.
      • Etkileşimden sonraki boyama (INP) 75. yüzdelikte 200 ms’yi geçmez; hareket sırasında 50 ms’den uzun görev olmaz.
      • Ürün rotasında hareket için en fazla 30 kB (gzip) JavaScript yüklenir; Rive yalnızca kullanıldığı rotada, sonradan yüklenir.
      • 5 saniyeden uzun ve kendiliğinden başlayan hareket durdurulabilir (WCAG 2.2.2). Sonsuz döngü yalnızca dönen göstergede vardır.
      • Saniyede üçten fazla yanıp sönme yoktur (WCAG 2.3.1).
      • Etkileşimle başlayan hareket kapatılabilir (WCAG 2.3.3, AAA hedefi): azaltılmış hareket ayarı ve ürün içi geçersiz kılma bunu sağlar.
      • Hareket bilgiyi tek başına taşımaz; durum metinle ve ekran okuyucu duyurusuyla da verilir.
      İş Araç
      Basma, üzerine gelme, odak, renk, giriş/çıkış, iskelet CSS ve hareket token’ları (varsayılan)
      Sayfa geçişi, ortak öğe View Transitions API (aynı belge)
      Kesilebilir yay, sürükleme, yerleşim (layout) canlandırması Motion; ADR sonrası
      İllüstrasyon ve marka animasyonu Rive; ADR sonrası, yalnız vektör dosya, kendi sunucumuzdaki WASM
      Hazır vektör animasyonu dotLottie; ADR sonrası, kendi sunucumuzdaki WASM ve worker
      lottie-web Kullanılmaz (eval gerektirir)
      • Her hareketin amacı yazılı mı (hikâyede motion.purpose)?
      • Süre, eğri ve mesafe yalnızca token’dan mı geliyor? Ham ms, cubic-bezier() yok mu?
      • Yalnızca saydamlık, dönüşüm ve renk mi canlanıyor?
      • Azaltılmış harekette yerine ne geliyor? 0,25× hızda kayıt incelendi mi?
      • Para akışında expressive eğri ya da taşan yay var mı?
      • 5 saniyeyi geçen ya da döngüye giren bir şey var mı; durdurulabiliyor mu?
      • Giriş hareketi yenilemede ya da filtrede tekrar ediyor mu?

      Token kullanın, azaltılmış hareketi token’lara bırakın

      Bölüm başlığı “Token kullanın, azaltılmış hareketi token’lara bırakın”
      tv({ base: [motion.press, ‘…’] })
      YapınHazır ayar (preset) süre, eğri ve azaltılmış hareketi token'lardan alır.
      duration-700 ease-in-out hover:scale-110
      YapmayınUzun ve rastgele süreler arayüzü yavaş hissettirir, azaltılmış harekette de çalışmaz.
      <span key={total} className={motion.reveal}>
      YapınYeni tutar kısa bir geçişle yerine gelir.
      countUp(0, balance, 1200)
      YapmayınSayarak artan bakiye, geçişte yanlış tutarlar gösterir.