บทนำสู่ Headless UI
ทำความเข้าใจแนวคิดคอมโพเนนต์แบบ headless ติดตั้ง @headlessui/react และสำรวจคอมโพเนนต์ที่มีให้ใช้ เช่น Menu, Dialog และ Listbox
บทนำสู่ Headless UI เป็นบทเรียน Tailwind CSS Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Tailwind CSS Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Tailwind CSS Academy มีบทเรียนทั้งหมด 4 บทเรียน
บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ
What Is a Headless Component?
A headless component provides behavior, state management, and accessibility without any built-in visual styling. It gives you all the keyboard navigation, ARIA attributes, and interaction logic — but leaves the visual presentation entirely to you. This is the opposite of a styled component library like Bootstrap, which bundles both behavior and appearance. Headless components let you apply your own Tailwind classes while getting accessibility for free.
Introducing Headless UI
Headless UI is a library of completely unstyled, accessible UI components built by the Tailwind Labs team. It provides components like Menu (dropdowns), Dialog (modals), Listbox (select menus), Combobox (autocomplete), Switch (toggles), Disclosure (accordions), and Tabs. All accessibility requirements — ARIA roles, keyboard navigation, focus management — are handled internally.
# Install for React
npm install @headlessui/react
# Install for Vue
npm install @headlessui/vue
# Available components:
# Menu, Dialog, Listbox, Combobox,
# Switch, Disclosure, Popover, RadioGroup,
# Tab (Tabs, TabGroup, TabList, TabPanel)
import { Menu, Dialog, Listbox } from '@headlessui/react';Why Headless UI With Tailwind?
Headless UI was designed with Tailwind CSS in mind — they are built by the same team. Every component accepts className props and exposes render props that give you state information (like open or active) to drive conditional Tailwind classes. Unlike component libraries that fight your custom styles, Headless UI components are a blank canvas that expects you to paint with Tailwind utilities.
import { Switch } from '@headlessui/react';
import { useState } from 'react';
function ToggleSwitch() {
const [enabled, setEnabled] = useState(false);
return (
<Switch
checked={enabled}
onChange={setEnabled}
className={cn(
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
enabled ? 'bg-blue-600' : 'bg-gray-200'
)}
>
<span className={cn(
'inline-block h-4 w-4 transform rounded-full bg-white transition-transform',
enabled ? 'translate-x-6' : 'translate-x-1'
)} />
</Switch>
);
}The Menu Component
The Menu component creates an accessible dropdown with keyboard navigation. It is composed of Menu.Button (the trigger), Menu.Items (the dropdown panel), and Menu.Item (each option). Headless UI handles opening/closing on click, closing on outside click, and keyboard arrow navigation automatically. You style each piece with Tailwind classes.
import { Menu } from '@headlessui/react';
function UserMenu() {
return (
<Menu as='div' className='relative'>
<Menu.Button className='flex items-center gap-2 rounded-lg px-3 py-2 hover:bg-gray-100'>
<img src='/avatar.png' className='h-8 w-8 rounded-full' alt='User' />
<span className='text-sm font-medium'>John Doe</span>
</Menu.Button>
<Menu.Items className='absolute right-0 mt-2 w-48 rounded-xl bg-white shadow-lg ring-1 ring-black/5 focus:outline-none'>
<div className='p-1'>
<Menu.Item>
{({ active }) => (
<button className={cn('w-full text-left px-3 py-2 text-sm rounded-lg', active && 'bg-gray-100')}>
Profile
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Menu>
);
}Render Props for State-Driven Styling
Headless UI components expose their internal state through render props. For example, Menu.Item provides an active boolean indicating whether the item is currently focused or hovered. Menu.Button exposes open. These booleans drive your conditional Tailwind classes, creating the visual distinction between states without writing any JavaScript event handlers yourself.
// Render props expose internal state
<Menu.Button>
{({ open }) => (
<span className={cn(
'flex items-center gap-2 px-4 py-2 rounded-lg',
open ? 'bg-blue-50 text-blue-600' : 'text-gray-700 hover:bg-gray-100'
)}>
Options
<ChevronDownIcon className={cn(
'h-4 w-4 transition-transform',
open && 'rotate-180'
)} />
</span>
)}
</Menu.Button>
// Menu.Item with active state
<Menu.Item>
{({ active, disabled }) => (
<button className={cn(
'w-full px-3 py-2 text-sm text-left rounded',
active && 'bg-blue-600 text-white',
disabled && 'opacity-50 cursor-not-allowed'
)}>
Edit
</button>
)}
</Menu.Item>The Dialog Component
The Dialog component creates fully accessible modals with built-in focus trapping and escape key handling. When opened, keyboard focus is automatically moved inside the dialog and constrained within it until the dialog closes. The Dialog.Panel contains the modal content, and Dialog.Overlay provides the backdrop. You apply all visual styles with Tailwind.
import { Dialog } from '@headlessui/react';
function ConfirmModal({ isOpen, onClose }) {
return (
<Dialog open={isOpen} onClose={onClose} className='relative z-50'>
{/* Backdrop */}
<div className='fixed inset-0 bg-black/40' aria-hidden='true' />
{/* Center the panel */}
<div className='fixed inset-0 flex items-center justify-center p-4'>
<Dialog.Panel className='bg-white rounded-2xl shadow-xl max-w-md w-full p-6'>
<Dialog.Title className='text-lg font-semibold text-gray-900'>
Confirm Action
</Dialog.Title>
<Dialog.Description className='mt-2 text-sm text-gray-600'>
Are you sure you want to continue?
</Dialog.Description>
<div className='mt-6 flex gap-3 justify-end'>
<button onClick={onClose} className='px-4 py-2 text-sm rounded-lg border'>Cancel</button>
<button className='px-4 py-2 text-sm rounded-lg bg-blue-600 text-white'>Confirm</button>
</div>
</Dialog.Panel>
</div>
</Dialog>
);
}The Disclosure Component
The Disclosure component implements accessible accordion or show/hide patterns. It provides Disclosure.Button (the toggle) and Disclosure.Panel (the expandable content). Headless UI handles the aria-expanded attribute on the button and associates it with the panel automatically. This is perfect for FAQ sections, collapsible navigation items, and settings panels.
import { Disclosure } from '@headlessui/react';
function FAQItem({ question, answer }) {
return (
<Disclosure as='div' className='border-b border-gray-200 py-4'>
{({ open }) => (
<>
<Disclosure.Button className='flex w-full justify-between items-center text-left'>
<span className='font-medium text-gray-900'>{question}</span>
<ChevronDownIcon className={cn(
'h-5 w-5 text-gray-500 transition-transform',
open && 'rotate-180'
)} />
</Disclosure.Button>
<Disclosure.Panel className='mt-3 text-sm text-gray-600 leading-relaxed'>
{answer}
</Disclosure.Panel>
</>
)}
</Disclosure>
);
}The Listbox Component
The Listbox component is an accessible alternative to the native <select> element, offering full keyboard navigation and ARIA compliance while being fully styleable with Tailwind. Use it when you need custom-styled option items with images, icons, or complex layouts that a native select cannot render. Listbox.Options and Listbox.Option compose the dropdown list.
import { Listbox } from '@headlessui/react';
const people = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
function PersonSelect({ value, onChange }) {
return (
<Listbox value={value} onChange={onChange}>
<div className='relative'>
<Listbox.Button className='w-full rounded-lg border border-gray-300 bg-white px-4 py-2 text-left text-sm'>
{value.name}
</Listbox.Button>
<Listbox.Options className='absolute z-10 mt-1 w-full rounded-xl bg-white shadow-lg ring-1 ring-black/5'>
{people.map(person => (
<Listbox.Option key={person.id} value={person}>
{({ active, selected }) => (
<div className={cn('px-4 py-2 text-sm cursor-pointer', active && 'bg-blue-50')}>
{selected && <CheckIcon className='inline h-4 w-4 mr-2 text-blue-600' />}
{person.name}
</div>
)}
</Listbox.Option>
))}
</Listbox.Options>
</div>
</Listbox>
);
}The Tabs Component
The Tab components in Headless UI create accessible tabbed interfaces with proper ARIA roles, keyboard navigation (arrow keys switch tabs), and automatic active state management. Compose Tab.Group, Tab.List, Tab, Tab.Panels, and Tab.Panel — Headless UI wires them together; Tailwind provides the visual treatment.
import { Tab } from '@headlessui/react';
function TabbedContent() {
return (
<Tab.Group>
<Tab.List className='flex gap-1 rounded-xl bg-gray-100 p-1'>
{['Overview', 'Analytics', 'Settings'].map(tab => (
<Tab
key={tab}
className={({ selected }) => cn(
'flex-1 rounded-lg py-2 text-sm font-medium transition-colors',
selected
? 'bg-white text-blue-700 shadow'
: 'text-gray-600 hover:text-gray-900'
)}
>
{tab}
</Tab>
))}
</Tab.List>
<Tab.Panels className='mt-4'>
<Tab.Panel className='p-4 rounded-xl bg-white'><p>Overview content</p></Tab.Panel>
<Tab.Panel className='p-4 rounded-xl bg-white'><p>Analytics content</p></Tab.Panel>
<Tab.Panel className='p-4 rounded-xl bg-white'><p>Settings content</p></Tab.Panel>
</Tab.Panels>
</Tab.Group>
);
}Headless UI Accessibility Guarantees
Headless UI provides tested, production-ready accessibility implementations that would take significant effort to write correctly yourself. The Dialog traps focus within the modal, the Menu implements the ARIA combobox/menu pattern with correct roles, and Listbox follows the ARIA Listbox pattern. These are verified against the ARIA Authoring Practices Guide, so you can trust the accessibility layer without writing a single ARIA attribute yourself.
<!-- What Headless UI generates automatically -->
<!-- For Menu.Button -->
<button
id='headlessui-menu-button-1'
type='button'
aria-haspopup='true'
aria-expanded='true'
aria-controls='headlessui-menu-items-2'
>
Options
</button>
<!-- For Menu.Items -->
<ul
id='headlessui-menu-items-2'
role='menu'
aria-labelledby='headlessui-menu-button-1'
tabindex='-1'
>
<li role='menuitem'>...</li>
</ul>
<!-- All generated automatically — no manual ARIA needed -->Composing Headless UI Components
Headless UI components can be composed together to build complex UI patterns. A settings page might combine Tab groups for navigation, Switch toggles for preferences, and a Dialog for confirmation modals — all in the same view. Each component handles its own accessibility independently, and Tailwind classes unify their visual style. This compositional approach lets you build sophisticated accessible interfaces with minimal custom JavaScript.
import { Tab, Switch, Dialog } from '@headlessui/react';
// Settings page combining multiple Headless UI components
function SettingsPage() {
return (
<Tab.Group>
<Tab.List class='flex gap-1 border-b border-gray-200 mb-6'>
<Tab class={({ selected }) =>
cn('px-4 py-2 text-sm font-medium border-b-2 -mb-px',
selected ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500')
}>Profile</Tab>
<Tab class={({ selected }) =>
cn('px-4 py-2 text-sm font-medium border-b-2 -mb-px',
selected ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500')
}>Notifications</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>{/* Profile settings with Switch toggles */}</Tab.Panel>
<Tab.Panel>{/* Notification settings */}</Tab.Panel>
</Tab.Panels>
</Tab.Group>
);
}Quick Check
Test your understanding of Tailwind CSS Mastery concepts from this lesson.
Lesson Recap
In this lesson you learned: Headless UI provides behavior and accessibility without visual styling, render props expose internal state (active, open, selected) for conditional Tailwind classes, and components like Menu, Dialog, Disclosure, Listbox, and Tab cover the most common accessible UI patterns. Next up we style a Headless UI Menu dropdown with Tailwind in detail.
คำถามที่พบบ่อย
บทเรียน “บทนำสู่ Headless UI” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “บทนำสู่ Headless UI” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Tailwind CSS Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Tailwind CSS Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “บทนำสู่ Headless UI”
ทำความเข้าใจแนวคิดคอมโพเนนต์แบบ headless ติดตั้ง @headlessui/react และสำรวจคอมโพเนนต์ที่มีให้ใช้ เช่น Menu, Dialog และ Listbox คุณปฏิบัติ Tailwind CSS Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Tailwind CSS Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Tailwind CSS Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “บทนำสู่ Headless UI” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Tailwind CSS Academy นี้ได้ไหม
ได้ บทเรียน Tailwind CSS Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- บทนำสู่ Headless UI
- การจัดสไตล์เมนูและเมนูดรอปดาวน์แบบ Headless
- การสร้างไดอะล็อกที่เข้าถึงได้
- การเปลี่ยนผ่านด้วย Headless UI