Tailwind CSS Academy · Ders

Headless UI ile Geçişler

Tailwind’in geçiş yardımcı sınıflarını kullanarak iletişim kutularının, açılır menülerin ve çekmecelerin giriş ve çıkış durumlarını canlandırmak için Transition bileşenini kullanın.

4. ders / 413 adım

Headless UI ile Geçişler, CoddyKit'te ücretsiz bir Tailwind CSS Academy dersidir. Bu, 4 dersinin 4. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, Tailwind CSS Academy öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Tailwind CSS Academy kursu toplamda 4 dersten oluşur.

Bu dersin bazı bölümleri henüz çevrilmemiş olup İngilizce olarak gösterilmektedir.

Why Animate With Headless UI's Transition?

Tailwind's transition-* utilities handle smooth state changes on persistent elements, but they cannot animate elements that mount and unmount from the DOM. When a dialog or dropdown appears, it goes from non-existent to visible in one frame — there is no transition. Headless UI's Transition component solves this by keeping the element mounted briefly during the exit animation, allowing Tailwind transition utilities to complete before the element is removed.

The Transition Component API

The Transition component from Headless UI accepts a show boolean and a set of class props: enter, enterFrom, enterTo for the entering animation, and leave, leaveFrom, leaveTo for the exit. Classes in enter and leave are active during the entire transition. Classes in enterFrom/leaveFrom define the start state, and enterTo/leaveTo define the end state.

import { Transition } from '@headlessui/react';

<Transition
  show={isVisible}
  enter='transition-opacity duration-200 ease-out'
  enterFrom='opacity-0'
  enterTo='opacity-100'
  leave='transition-opacity duration-150 ease-in'
  leaveFrom='opacity-100'
  leaveTo='opacity-0'
>
  <div className='bg-white rounded-xl p-6 shadow-lg'>
    Content that fades in and out
  </div>
</Transition>

Animating a Dialog Backdrop and Panel

A polished dialog entrance uses two separate animations: the backdrop fades in while the panel scales and fades in from a slightly smaller size. Wrap each element in its own Transition.Child and set different durations so the backdrop appears slightly before the panel. Use Transition as the outer wrapper with the show prop, and Transition.Child for coordinated child animations.

import { Dialog, Transition } from '@headlessui/react';
import { Fragment } from 'react';

function AnimatedDialog({ open, onClose, children }) {
  return (
    <Transition show={open} as={Fragment}>
      <Dialog onClose={onClose} className='relative z-50'>

        {/* Backdrop fade */}
        <Transition.Child
          as={Fragment}
          enter='ease-out duration-300'
          enterFrom='opacity-0'
          enterTo='opacity-100'
          leave='ease-in duration-200'
          leaveFrom='opacity-100'
          leaveTo='opacity-0'
        >
          <div className='fixed inset-0 bg-black/50' aria-hidden='true' />
        </Transition.Child>

        {/* Panel scale-in */}
        <div className='fixed inset-0 flex items-center justify-center p-4'>
          <Transition.Child
            as={Fragment}
            enter='ease-out duration-300'
            enterFrom='opacity-0 scale-95'
            enterTo='opacity-100 scale-100'
            leave='ease-in duration-200'
            leaveFrom='opacity-100 scale-100'
            leaveTo='opacity-0 scale-95'
          >
            <Dialog.Panel className='bg-white rounded-2xl shadow-2xl max-w-md w-full p-6'>
              {children}
            </Dialog.Panel>
          </Transition.Child>
        </div>

      </Dialog>
    </Transition>
  );
}

Animating Dropdowns and Popovers

Dropdown menus benefit from a subtle slide-and-fade entrance. Wrap the Menu.Items in a Transition component that slides down from the trigger and fades in. The exit animation should be faster than the entrance — a 100-150ms leave feels crisp and responsive. Use transform origin-top so the scale animation originates from the top where the button is.

import { Menu, Transition } from '@headlessui/react';
import { Fragment } from 'react';

<Menu as='div' className='relative'>
  <Menu.Button>Options</Menu.Button>

  <Transition
    as={Fragment}
    enter='transition ease-out duration-150'
    enterFrom='transform opacity-0 scale-95 -translate-y-2'
    enterTo='transform opacity-100 scale-100 translate-y-0'
    leave='transition ease-in duration-100'
    leaveFrom='transform opacity-100 scale-100 translate-y-0'
    leaveTo='transform opacity-0 scale-95 -translate-y-2'
  >
    <Menu.Items className='absolute right-0 mt-1 w-48 rounded-xl bg-white shadow-lg ring-1 ring-black/5 p-1 focus:outline-none'>
      {/* items */}
    </Menu.Items>
  </Transition>
</Menu>

Slide-In Sidebar Drawer

A mobile sidebar drawer slides in from the left or right edge. Use a Transition with translate-x utilities for the slide direction. Start off-screen (-translate-x-full for left, translate-x-full for right) and translate to translate-x-0 to bring it into view. Add a backdrop transition simultaneously using Transition.Child.

function MobileSidebar({ open, onClose }) {
  return (
    <Transition show={open} as={Fragment}>
      <Dialog onClose={onClose}>
        {/* Backdrop */}
        <Transition.Child
          as={Fragment}
          enter='ease-out duration-300'
          enterFrom='opacity-0'
          enterTo='opacity-100'
          leave='ease-in duration-200'
          leaveFrom='opacity-100'
          leaveTo='opacity-0'
        >
          <div className='fixed inset-0 bg-black/40 z-40' aria-hidden='true' />
        </Transition.Child>

        {/* Sidebar panel */}
        <Transition.Child
          as={Fragment}
          enter='transition ease-out duration-300'
          enterFrom='-translate-x-full'
          enterTo='translate-x-0'
          leave='transition ease-in duration-200'
          leaveFrom='translate-x-0'
          leaveTo='-translate-x-full'
        >
          <Dialog.Panel className='fixed left-0 top-0 h-full w-72 bg-white shadow-xl z-50 overflow-y-auto'>
            {/* sidebar content */}
          </Dialog.Panel>
        </Transition.Child>
      </Dialog>
    </Transition>
  );
}

Notification Toast Animation

Notification toasts typically slide in from the bottom or top corner and fade out after a timeout. Use a Transition with translate-y for the slide direction. Combine show with a boolean that automatically toggles to false after a timeout using setTimeout in a useEffect. The leave animation completes before the component unmounts.

function Toast({ message, show, onHide }) {
  return (
    <Transition
      show={show}
      as={Fragment}
      enter='transition ease-out duration-300'
      enterFrom='translate-y-4 opacity-0'
      enterTo='translate-y-0 opacity-100'
      leave='transition ease-in duration-200'
      leaveFrom='translate-y-0 opacity-100'
      leaveTo='translate-y-4 opacity-0'
    >
      <div className='
        fixed bottom-4 right-4 z-50
        bg-gray-900 text-white
        px-4 py-3 rounded-xl shadow-lg
        flex items-center gap-3
        max-w-sm
      '>
        <CheckCircleIcon className='h-5 w-5 text-green-400 flex-shrink-0' />
        <span className='text-sm'>{message}</span>
        <button onClick={onHide} className='ml-auto text-gray-400 hover:text-white'>
          <XMarkIcon className='h-4 w-4' />
        </button>
      </div>
    </Transition>
  );
}

Coordinating Multiple Transitions

The Transition.Child component coordinates timing with a parent Transition. Children observe the parent's show state and can add their own timing offsets. This enables staggered animations where different parts of a UI appear in sequence. Headless UI does not natively support arbitrary stagger delays, but you can simulate it with incrementally longer enter durations or CSS animation delays.

// Staggered items using animation-delay workaround
<Transition show={open}>
  <div className='bg-white rounded-xl p-6 shadow-lg'>
    {items.map((item, i) => (
      <Transition.Child
        key={item.id}
        as={Fragment}
        enter='transition ease-out duration-200'
        enterFrom='opacity-0 translate-y-2'
        enterTo='opacity-100 translate-y-0'
        style={{ transitionDelay: i * 50 + 'ms' }}
        leave='transition ease-in duration-150'
        leaveFrom='opacity-100'
        leaveTo='opacity-0'
      >
        <div className='py-2 border-b border-gray-100'>{item.label}</div>
      </Transition.Child>
    ))}
  </div>
</Transition>

Transition With appear Prop

By default, the Transition component does not animate on first render if show is already true when it mounts. The appear prop overrides this, causing the enter transition to play even on initial mount. This is useful for page-load animations or when a component is conditionally rendered with show immediately set to true.

// Without 'appear': no animation on initial mount when show=true
<Transition show={true}>
  <div>This appears without animation on first load</div>
</Transition>

// With 'appear': animates in even on first mount
<Transition
  show={true}
  appear  // enables enter transition on initial mount
  enter='transition-all ease-out duration-500'
  enterFrom='opacity-0 scale-95'
  enterTo='opacity-100 scale-100'
>
  <div className='bg-white rounded-xl p-6 shadow'>
    This animates in on first load
  </div>
</Transition>

Handling afterLeave Callbacks

The afterLeave prop fires a callback after the leave transition completes. This is useful for cleanup actions that should happen only after the element has fully disappeared — like resetting form state, clearing error messages, or unmounting expensive child components. Running cleanup during the transition (when the element is still visible) would cause a jarring visual change.

function SearchDialog({ open, onClose }) {
  const [query, setQuery] = useState('');

  return (
    <Transition
      show={open}
      afterLeave={() => {
        // Reset query AFTER dialog has fully faded out
        // Prevents seeing the input clear while dialog is still visible
        setQuery('');
      }}
      enter='ease-out duration-300'
      enterFrom='opacity-0 scale-95'
      enterTo='opacity-100 scale-100'
      leave='ease-in duration-200'
      leaveFrom='opacity-100 scale-100'
      leaveTo='opacity-0 scale-95'
    >
      <Dialog open={open} onClose={onClose}>
        <Dialog.Panel className='bg-white rounded-2xl p-6 shadow-xl'>
          <input
            value={query}
            onChange={e => setQuery(e.target.value)}
            placeholder='Search...'
            className='w-full border rounded-lg px-3 py-2'
          />
        </Dialog.Panel>
      </Dialog>
    </Transition>
  );
}

Best Practices for UI Transitions

Keep animations short and purposeful. Enter transitions should be 150-300ms — long enough to feel smooth but short enough not to make users wait. Leave transitions should be 10-30% shorter than enters — exits should feel crisp. Use ease-out for enters (fast start, slow finish feels natural) and ease-in for exits (slow start, fast finish feels intentional). Never animate for the sake of animation — every motion should communicate state change.

/* Recommended timing guidelines */

/* Tooltip / small dropdown */
enter: 'transition ease-out duration-100'
leave: 'transition ease-in duration-75'

/* Modal dialog */
enter: 'ease-out duration-300'
leave: 'ease-in duration-200'

/* Sidebar drawer */
enter: 'transition ease-out duration-300'
leave: 'transition ease-in duration-250'

/* Page transitions */
enter: 'transition ease-out duration-500'
leave: 'transition ease-in duration-300'

/* Avoid: too slow (>500ms) feels laggy */
/* Avoid: ease-in for enters (starts slow, looks stuck) */

Respecting Reduced Motion Preferences

Some users experience motion sickness or seizures from on-screen animations. The prefers-reduced-motion media query allows the operating system to signal this preference to the browser. Always respect it in your Transition animations. Use Tailwind's motion-reduce: variant (or a custom variant) to strip transitions for users who need reduced motion, keeping the UI functional but static.

/* globals.css — respect reduced motion globally */
@media (prefers-reduced-motion: reduce) {
  .transition,
  .transition-all,
  .transition-colors,
  .transition-transform {
    transition-duration: 0.01ms !important;
  }
}

<!-- In Tailwind with motion-reduce: variant -->
<div
  class='
    transition-all ease-out duration-300
    motion-reduce:transition-none
    opacity-0 translate-y-4
    open:opacity-100 open:translate-y-0
  '
>
  Animates for standard users, instant for reduced-motion users
</div>

Quick Check

Test your understanding of Tailwind CSS Mastery concepts from this lesson.

Lesson Recap

In this lesson you learned: Transition wraps mounted elements to enable enter/leave animations, Transition.Child coordinates timing for multiple elements in a parent Transition, and afterLeave runs cleanup callbacks only after the exit animation completes. Next up we dive into writing custom Tailwind plugins to extend the utility system.

Başlamak ücretsiz

Yapay zeka eğitmeniyle HTML öğren — ücretsiz

Tarayıcında gerçek kod yaz ve çalıştır, 7/24 yapay zeka eğitmeninden anında yardım al; web'de ya da uygulamada kaldığın yerden devam et.

Kurslar
30
Dersler
120

Sıkça Sorulan Sorular

“Headless UI ile Geçişler” dersi ücretsiz mi?

Evet — “Headless UI ile Geçişler” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve Tailwind CSS Academy kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Tailwind CSS Academy kursu toplamda 4 dersten oluşur.

“Headless UI ile Geçişler” dersinde ne öğreneceğim?

Tailwind’in geçiş yardımcı sınıflarını kullanarak iletişim kutularının, açılır menülerin ve çekmecelerin giriş ve çıkış durumlarını canlandırmak için Transition bileşenini kullanın. Tailwind CSS Academy ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.

Tailwind CSS Academy öğrenmeye başlamak için deneyim gerekli mi?

Önceden deneyim gerekmez. CoddyKit'te Tailwind CSS Academy, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 4. dersidir.

“Headless UI ile Geçişler” dersi ne kadar sürer?

Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.

Bu Tailwind CSS Academy dersinde kod yazıp çalıştırabilir miyim?

Evet. Her Tailwind CSS Academy dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.

Bu kursun tüm dersleri

  1. Headless UI’ye Giriş
  2. Headless Menü ve Açılır Menüyü Biçimlendirme
  3. Erişilebilir İletişim Kutuları Oluşturma
  4. Headless UI ile Geçişler
← Tailwind CSS Academy Sayfasına Dön