# Headless UI 디자인 시스템

## Headless UI 디자인 시스템 인터페이스 미리 보기

공유 대화상자를 열고, 이메일을 검증하고, 키보드로 권한을 선택하고, 이 페이지에만 적용되는 초대를 추가한 뒤, 공개 링크 상태를 전환해 보세요.

## Headless UI 디자인 시스템 디자인 레퍼런스

Headless UI에는 공식 시각 토큰 스케일이 없습니다. 아래의 모든 숫자 및 색상 값은 이 OpenDesign 예시에만 해당합니다. 링크된 문서는 동작과 상태 API를 설명합니다. 테스트한 패키지: @headlessui/react 2.2.10.

### 독자적으로 정한 CSS 색상 역할

| 이름 / 규칙 | 값 / 구성 | 용도 |
| --- | --- | --- |
| --canvas | #f5f5f4 | 문서 작업 공간. |
| --surface | #ffffff | 종이 영역, 접근 권한 패널, 대화상자. |
| --ink | #1c1917 | 기본 텍스트. |
| --muted | #57534e | 보조 텍스트. |
| --line | #d6d3d1 | 패널과 입력란 테두리. |
| --accent | #4338ca | 주요 동작과 포커스 윤곽선. |
| --accent-soft | #eef2ff | 포커스된 옵션과 아바타 배경. |
| --error | #b91c1c | 유효하지 않은 이메일 텍스트와 테두리. |
### 독자적인 간격 및 기하 구조

| 이름 / 규칙 | 값 / 구성 | 용도 |
| --- | --- | --- |
| --space-1 / --space-2 | 4px / 8px | 작은 간격 단위. |
| --space-3 / --space-4 | 12px / 16px | 컨트롤 간 간격과 모바일 화면 안쪽 여백. |
| --space-6 / --space-8 | 24px / 32px | 패널 안쪽 여백과 주요 요소 간 간격. |
| --radius-control | 8px | 버튼과 입력란. |
| --radius-panel | 16px | 종이 영역과 대화상자. |
| .dialog-panel width | min(100%, 460px) | 좁은 화면에서 크기를 제한하는 오버레이. |
| .dialog-panel max-height | calc(100% - 16px) | 고정 위치 지정 컨테이너 안쪽에 여백을 둡니다. |
### 독자적으로 정한 글꼴과 반응형 설정

| 이름 / 규칙 | 값 / 구성 | 용도 |
| --- | --- | --- |
| Body | 16px / 1.5 system-ui | 외부 글꼴이 필요하지 않습니다. |
| Screen title | 28px / 1.25 | 문서의 정체성을 나타냅니다. |
| Document headline | 32px; 26px below 700px | 편집 콘텐츠의 위계를 나타냅니다. |
| Dialog heading | 20px / 1.3 | 공유 작업을 설명합니다. |
| Labels and helper copy | 14px | 필드에 대한 안내. |
| Error and access description | 13px | 지속적으로 표시되는 피드백. |
| Breakpoint | 700px | 문서 패널과 접근 권한 패널을 세로로 배치합니다. |
### 네이티브 상호작용 규칙

| 이름 / 규칙 | 값 / 구성 | 용도 |
| --- | --- | --- |
| Dialog open / onClose | Controlled boolean / dismissal callback | Escape 키 입력과 바깥 영역 클릭은 닫기 요청을 보냅니다. 저장되지는 않습니다. |
| DialogPanel | Content boundary | 배경과 위치 지정 래퍼는 이 경계 바깥에 있습니다. |
| Listbox value / onChange | Controlled permission string | 키보드로 항목을 이동하면 선택된 권한이 바뀝니다. |
| data-focus / data-selected | Native state attributes | CSS는 키보드 포커스와 확정된 선택을 구분합니다. |
| Switch checked / onChange | Controlled boolean | data-checked가 시각적 상태를 지정합니다. |
## Headless UI 디자인 시스템 전체 가이드

실제 인터페이스를 위해 이 시스템을 선택하고 적용하고 검토하는 실용 가이드.

완전한 안내와 구현 시작점, 소스 링크.

## Headless UI 디자인 시스템: 화면을 꾸미기 전에 동작을 구성하세요

### 화면을 꾸미기 전에 동작을 구성하세요

> Dialog, Listbox, Switch는 각각 서로 다른 상호작용을 다룹니다. Dialog는 범위가 정해진 작업과 그 작업의 포커스 맥락을 제어하고, Listbox는 확정된 선택 항목 하나를 나타내며, Switch는 독립적인 불리언 설정을 나타냅니다. 이 요소들을 조합하더라도 상태까지 하나로 합칠 필요는 없습니다. 초대 초안, 선택한 권한, 공개 링크 설정은 각각 별도로 유지됩니다.

이 예시에서는 React 컴포넌트 API를 사용하며, DialogPanel과 ListboxButton 등을 직접 가져옵니다. 대화상자 안에 목록 상자가 있으므로 기존 오버레이 안에서도 팝업 위치와 닫기 동작이 제대로 작동해야 합니다. 먼저 이러한 조합을 구성한 다음, 바깥에 문서 패널과 접근 권한 요약을 추가하세요. 제공된 CSS는 이 네이티브 요소의 전체적인 외관을 보여 줍니다.

## Headless UI 디자인 시스템: Dialog, Listbox, Switch 구성

### 오버레이 경계를 의도적으로 설정하세요

DialogBackdrop은 고정 위치 지정 컨테이너의 형제 요소입니다. DialogPanel은 컨테이너 안에 배치되는 경계가 분명한 흰색 패널로, 화면 전체를 채우지 않습니다. DialogTitle은 작업을 설명하고, Description은 닫을 때 어떤 일이 일어나는지 안내합니다. 네이티브 입력 요소에는 data-autofocus가 있으며, Headless UI가 대화상자 주변의 포커스 동작을 관리합니다.

지원 사양 읽기

### 목록 상자는 값을 선택하고, 스위치는 불리언 값을 바꿉니다

ListboxButton에는 선택된 역할이 표시됩니다. ListboxOptions는 anchor="bottom"과 --button-width 기준 너비를 사용하므로 팝업이 트리거와 나란히 정렬됩니다. 각 ListboxOption에는 안정적인 문자열 값이 지정됩니다. 네이티브 data-focus 및 data-selected 속성은 서로 다른 두 가지 CSS 스타일을 적용합니다.

공개 링크 Switch는 이름 없는 색상 알약 모양 요소로 만들지 않고 Label이 포함된 Field 안에 배치합니다. 스위치를 켜거나 끄면 접근성 지원 컨트롤과 화면에 표시되는 설명 문장이 함께 바뀝니다. 이 페이지에만 적용되는 초대는 공개 링크 설정과 별개입니다.

지원 사양 읽기

## Headless UI 디자인 시스템: 선택, 닫기, 페이지 내 확인을 분리하기

### 이 예시에서 사용하는 상태 모델

대화상자를 열면 새 수신자 초안과 Can view 권한이 초기화됩니다. 취소, Escape 키 입력, 바깥 영역 클릭은 대화상자를 닫지만 수신자를 추가하지 않습니다. 제출하면 이메일을 검증하고 이미 있는 주소인지 확인한 뒤, 초대를 메모리에 추가하고 대화상자를 닫습니다. 확인 메시지에는 이메일이 전송되지 않았음을 명시합니다.

권한 목록 상자에서 변경하는 것은 초안의 역할이지 기존 수신자의 접근 권한이 아닙니다. 공개 링크 상태는 별도의 불리언 값을 변경합니다. 이렇게 상태를 분리하면 닫기 이벤트가 모두 권한 저장으로 이어진다고 잘못 처리하는 일을 방지할 수 있습니다.

- 입력이 올바르지 않은 경우: 대화상자를 연 상태로 유지하고 오류를 이메일 입력란에 연결합니다.

- 수신자가 중복된 경우: 기존 접근 권한 목록을 변경하지 않습니다.

- 페이지 내 초대가 정상적으로 처리된 경우: 목록을 업데이트하고 미리 보기 전용 결과를 알립니다.

## Headless UI 디자인 시스템: 스타일 선택과 키보드 포커스 위치를 각각 표시하기

### 스타일 선택과 키보드 포커스 위치를 각각 표시하기

ListboxOption은 서로 다른 상태를 나타내는 data-focus와 data-selected를 제공합니다. 포커스된 옵션에는 옅은 강조 스타일이 적용되고, 확정된 권한에는 체크 표시가 나타납니다. 따라서 키보드 사용자는 현재 선택을 놓치지 않고 다른 옵션을 탐색할 수 있습니다.

팝업 밖에서도 같은 구분이 적용됩니다. 오류 역할은 유효하지 않은 수신자 필드에 속하고, Switch의 선택됨 표시는 공개 링크 접근 권한에 속합니다. CSS은 설명하려는 컴포넌트 상태에 맞게 범위를 지정하세요. 포커스되거나 선택된 모든 요소에 똑같은 색을 적용하는 광범위한 선택자는 서로 다른 의미를 흐릴 수 있습니다.

```
:root { --accent: #4338ca; --accent-soft: #eef2ff; --error: #b91c1c; }
.role-option[data-focus] { background: var(--accent-soft); }
.role-option[data-selected] .selected { visibility: visible; }
```

코드 참조

## Headless UI 디자인 시스템: 문서 정보와 권한을 알아보기 쉽게 표시하기

### 재현 가능한 타이포그래피 설정

문서 제목은 28px, 줄 높이는 1.25이고 대화상자 제목은 20px, 줄 높이는 1.3입니다. 본문은 플랫폼 시스템 글꼴로 16px/1.5를 적용합니다. 필드 레이블과 도움말 문구는 14px입니다. 정확한 값은 style.css에 있으므로 화면 캡처를 보고 추측할 필요가 없습니다.

접근 목록의 이메일에는 overflow-wrap:anywhere를 적용해 주소가 열 너비를 강제로 늘리지 않도록 합니다. 권한은 별도 줄에 표시됩니다. 대화상자에서는 초대를 추가하는 순간 전체 문서 제목과 선택한 권한을 확인할 수 있습니다.

## Headless UI 디자인 시스템: 오버레이 배치와 반응형 CSS을 완성하기

### 필수 전제 조건을 숨기지 않습니다

제공된 스타일시트에는 전체 오버레이가 포함되어 있습니다. 고정 인셋 배치, 배경 불투명도, z-index, 최대 너비, 세로 스크롤, 패딩을 모두 설정합니다. 700px 미만에서는 문서 패널이 세로로 쌓이고 대화상자의 패딩이 줄어듭니다. Tailwind 빌드는 전제하지 않습니다.

배치 컨테이너와 DialogPanel을 분리하세요. 컨테이너는 사용 가능한 뷰포트를 덮고 가장자리 여백을 제공하며, 패널은 너비가 제한된 콘텐츠 표면을 제공합니다. 대화상자 콘텐츠가 길어질 수 있으므로 스크롤 영역을 지정해 좁은 화면에서도 수신자 필드, 권한 선택, 닫기 동작에 접근할 수 있도록 하세요.

```
.dialog-position { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; padding: 16px; overflow-y: auto; }
.dialog-panel { width: min(100%, 460px); max-height: calc(100% - 16px); overflow-y: auto; }
```

코드 참조

## Headless UI 디자인 시스템: 기본 상태와 포커스 동작 활용하기

### 키보드 동작 확인

대화상자를 열고 수신자 필드에 초기 포커스가 놓이는지 확인합니다. Tab 키로 권한 트리거에 이동한 다음 화살표 키와 Enter 키로 역할을 선택합니다. Escape 키는 현재 포커스 맥락에 따라 활성 팝업 또는 대화상자를 닫습니다. 대화상자를 닫은 뒤에는 포커스가 공유 트리거로 돌아와야 합니다.

이메일 필드는 사용자 지정 오류에 aria-invalid와 aria-describedby를 사용합니다. 옵션의 체크 표시는 Listbox가 선택 상태를 전달하므로 장식용입니다. 공개 링크 컨트롤에는 표시되는 Label이 있고, 전환 후에도 설명 문장은 그대로 유지됩니다. Reduced-motion CSS 설정에서는 장식용 스위치 손잡이의 전환 효과가 제거됩니다.

지원 사양 읽기

## Headless UI 디자인 시스템: 하나의 React API 생성 방식을 일관되게 사용하기

### 하나의 React API 생성 방식을 일관되게 사용하기

전체 React 진입점은 App을 마운트하고, App의 스타일시트는 오버레이의 크기와 배치 및 상태 선택자를 제공합니다. 검증된 2.2.10 예제에서는 DialogPanel, DialogBackdrop, ListboxButton, ListboxOptions를 직접 가져옵니다. 이전의 복합 컴포넌트 표기는 이 구성에 섞지 말고 의도적으로 마이그레이션해야 합니다.

ListboxOptions는 anchor="bottom"과 기본 제공되는 --button-width 변수를 사용해 옵션 목록을 트리거에 맞춰 배치합니다. 이러한 설정은 선택 사항인 장식이 아니라 통합의 일부입니다. 배치 컨테이너를 제거하거나 너비를 관련 없는 고정값으로 대체하면 닫힌 필드가 제대로 보여도 중첩 팝업이 어긋날 수 있습니다.

1. React 프로젝트에 @headlessui/react를 설치합니다.

2. 예제에서 App.jsx와 style.css를 저장한 다음, 안내된 대로 main.jsx를 바꿉니다.

3. 개발 서버를 실행하고 대화상자 닫기, 권한 선택, 중복 이메일 복구를 테스트합니다.

## Headless UI 디자인 시스템: 실제 권한 상호작용 확인하기

### 검수 조건

유용한 검토는 빈 컴포넌트 갤러리가 아니라 문서와 기존 수신자가 있는 상태에서 시작합니다. 유효한 초대를 ‘댓글 작성 가능’ 권한으로 추가한 뒤, 다시 열어 같은 이메일을 입력해 보세요. 기존 행이 유지되고 중복에 대해 구체적인 오류가 표시되는지 확인합니다. 공개 링크를 켜고 별도의 설명도 확인합니다.

이 예제는 로컬 UI 상태만 보여 줍니다. 수신자 검색 기능이나 공유 백엔드는 제공하지 않습니다. 실제 서비스에 연결하려면 대기 중 및 오류 응답 모델과 권한 변경이 동시에 발생할 때 적용할 명시적인 정책이 필요합니다. 기본 구성 요소는 권한 부여 규칙을 결정하지 않습니다.

## Headless UI 디자인 시스템 컴포넌트 예제

### App.jsx — 기본 공유 구성

제어되는 Dialog, 권한 Listbox, 레이블이 지정된 Switch, 로컬 유효성 검사를 모두 포함합니다.

```
import React, { useState } from 'react';
import { Dialog, DialogBackdrop, DialogPanel, DialogTitle, Description, Listbox, ListboxButton, ListboxOptions, ListboxOption, Switch, Field, Label } from '@headlessui/react';
import './style.css';
const roles = ['Can view', 'Can comment', 'Can edit'];
export default function App() {
 const [open,setOpen]=useState(false),[role,setRole]=useState(roles[0]),[publicLink,setPublicLink]=useState(false),[email,setEmail]=useState(''),[error,setError]=useState(''),[notice,setNotice]=useState('');
 const [people,setPeople]=useState([{email:'alex@example.test',role:'Can edit'}]);
 const start=()=>{setEmail('');setError('');setRole(roles[0]);setOpen(true)};
 const submit=e=>{e.preventDefault();if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)){setError('Enter a complete email address.');return;}if(people.some(p=>p.email===email)){setError('This person already appears in the access list.');return;}setPeople([...people,{email,role}]);setOpen(false);setNotice('Invitation added to this local example only. No email was sent.');};
 return <main className='sharing-shell'>
  <header><div><span className='eyebrow'>HEADLESS UI / CUSTOM SURFACE</span><h1>Quarterly brief</h1><p className='muted'>Research · Updated today</p></div><button className='primary' onClick={start}>Share document</button></header>
  <div className='document-grid'><article className='paper'><span className='tag'>WORKING DOCUMENT</span><h2>Make the next decision clearer.</h2><p>Three research priorities for the coming quarter, shared with the people who need to review them.</p><div className='rule'/><h3>01 / Understand the handoff</h3><p>Compare where context is lost between research notes and the next product decision.</p><h3>02 / Review access deliberately</h3><p>Keep editing rights separate from the ability to read a document.</p></article>
  <aside className='access'><h2>People with access</h2><p className='muted'>The list below is local demonstration state.</p>{people.map((p,i)=><div className='person' key={p.email}><span className='avatar'>{i===0?'AK':'+'}</span><div><strong>{p.email}</strong><small>{p.role}</small></div></div>)}<Field className='link-setting'><Label>Public link</Label><Switch checked={publicLink} onChange={setPublicLink} className='switch'><span /></Switch></Field><p className='link-detail'>{publicLink?'Anyone with the example link could view this document. This changes only the local preview.':'Restricted to the named people in this example.'}</p><p role='status' className='notice'>{notice}</p></aside></div>
  <Dialog open={open} onClose={()=>setOpen(false)} className='dialog-root'><DialogBackdrop className='backdrop'/><div className='dialog-position'><DialogPanel className='dialog-panel'><DialogTitle as='h2'>Share quarterly brief</DialogTitle><Description className='muted'>Choose one recipient and a permission. Closing discards this invitation draft.</Description><form noValidate onSubmit={submit}><label className='field-label' htmlFor='recipient'>Recipient email</label><input id='recipient' data-autofocus type='email' value={email} onChange={e=>{setEmail(e.target.value);setError('')}} aria-invalid={!!error} aria-describedby={error?'email-error':undefined} placeholder='jules@example.test'/>{error&&<p id='email-error' role='alert' className='error'>{error}</p>}<Listbox value={role} onChange={setRole}><Label className='field-label'>Permission</Label><ListboxButton className='role-button'>{role}<span aria-hidden='true'>⌄</span></ListboxButton><ListboxOptions anchor='bottom' className='role-options'>{roles.map(value=><ListboxOption key={value} value={value} className='role-option'><span>{value}</span><span className='selected' aria-hidden='true'>✓</span></ListboxOption>)}</ListboxOptions></Listbox><p className='muted permission-help'>{role==='Can edit'?'May change document content in this example.':role==='Can comment'?'May add comments without editing content.':'May read the document without changing it.'}</p><div className='dialog-actions'><button type='button' onClick={()=>setOpen(false)}>Cancel</button><button className='primary' type='submit'>Add invitation</button></div></form></DialogPanel></div></Dialog>
  <footer>Native Dialog, Listbox, and Switch behavior. Original CSS below supplies all visual values; Headless UI does not ship this palette.</footer>
 </main>;
}
```

코드 참조

- 이메일이나 요청은 전송되지 않습니다.

- Escape 키를 누르거나 취소하면 저장되지 않은 초대가 폐기됩니다.

### style.css — 완성된 사용자 지정 시각 시스템

App.jsx에 필요한 시각 토큰, 상태 선택자, 오버레이 배치, 반응형 규칙을 모두 포함합니다. 이 설정은 예제에만 해당하며 공식 Headless UI 기본값이 아닙니다.

```
:root{--canvas:#f5f5f4;--surface:#fff;--ink:#1c1917;--muted:#57534e;--line:#d6d3d1;--accent:#4338ca;--accent-soft:#eef2ff;--error:#b91c1c;--space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-6:24px;--space-8:32px;--radius-control:8px;--radius-panel:16px;font:16px/1.5 system-ui,sans-serif;color:var(--ink);background:var(--canvas)}*{box-sizing:border-box}body{margin:0}button,input{font:inherit}button{cursor:pointer;border:1px solid var(--line);border-radius:var(--radius-control);padding:10px 16px;background:var(--surface);color:var(--ink);font-weight:600}button:focus-visible,input:focus-visible{outline:2px solid var(--accent);outline-offset:3px}.primary{background:var(--accent);color:white;border-color:var(--accent)}.sharing-shell{max-width:1120px;margin:auto;padding:32px}.sharing-shell>header{display:flex;align-items:center;justify-content:space-between;gap:24px;margin-bottom:32px}.eyebrow{font-size:11px;letter-spacing:.1em;color:var(--accent);font-weight:700}h1{font-size:28px;line-height:1.25;margin:8px 0}h2{font-size:20px;line-height:1.3;margin:0 0 12px}h3{font-size:16px;margin:24px 0 8px}p{margin:0 0 16px}.muted{color:var(--muted);font-size:14px}.document-grid{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(260px,1fr);gap:24px}.paper,.access{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-panel);padding:32px}.paper h2{font-size:32px;max-width:15ch;margin-top:24px}.paper p{max-width:48ch}.tag{font-size:11px;font-weight:600;border:1px solid var(--line);padding:4px 8px;border-radius:4px}.rule{height:1px;background:var(--line);margin:24px 0}.person{display:flex;gap:12px;padding:16px 0;border-bottom:1px solid var(--line)}.person strong{display:block;font-size:14px;overflow-wrap:anywhere}.person small{color:var(--muted)}.avatar{flex:none;width:32px;height:32px;border-radius:50%;background:var(--accent-soft);color:var(--accent);display:grid;place-items:center;font-size:12px}.link-setting{display:flex;align-items:center;justify-content:space-between;margin-top:24px;font-weight:600}.switch{padding:3px;width:44px;height:26px;border:0;border-radius:999px;background:#a8a29e}.switch span{display:block;width:20px;height:20px;border-radius:50%;background:white;transition:transform .15s}.switch[data-checked]{background:var(--accent)}.switch[data-checked] span{transform:translateX(18px)}.link-detail{margin-top:12px;font-size:13px;color:var(--muted)}.notice{font-size:13px;color:var(--accent)}footer{font-size:12px;color:var(--muted);margin-top:24px}.dialog-root{position:fixed;inset:0;z-index:20}.backdrop{position:fixed;inset:0;background:rgb(28 25 23/.45)}.dialog-position{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:16px;overflow-y:auto}.dialog-panel{width:min(100%,460px);max-height:calc(100% - 16px);overflow-y:auto;background:var(--surface);padding:32px;border-radius:var(--radius-panel);box-shadow:0 24px 64px rgb(0 0 0/.22)}.field-label{display:block;font-size:14px;font-weight:600;margin:20px 0 8px}input[type=email]{display:block;width:100%;padding:10px 12px;border:1px solid var(--line);border-radius:var(--radius-control);background:white;color:var(--ink)}input[aria-invalid=true]{border-color:var(--error)}.error{font-size:13px;color:var(--error);margin:8px 0}.role-button{display:flex;justify-content:space-between;width:100%;text-align:left}.role-options{z-index:30;width:var(--button-width);--anchor-gap:4px;background:var(--surface);border:1px solid var(--line);padding:4px;border-radius:8px;box-shadow:0 8px 24px rgb(0 0 0/.12);outline:none}.role-option{display:flex;justify-content:space-between;padding:10px;border-radius:4px;cursor:pointer;font-size:14px}.role-option[data-focus]{background:var(--accent-soft)}.role-option .selected{visibility:hidden;color:var(--accent)}.role-option[data-selected] .selected{visibility:visible}.permission-help{margin-top:12px}.dialog-actions{display:flex;justify-content:flex-end;gap:12px;margin-top:24px}@media(max-width:700px){.sharing-shell{padding:20px}.sharing-shell>header{align-items:flex-start;flex-direction:column}.document-grid{grid-template-columns:1fr}.paper,.access{padding:24px}.paper h2{font-size:26px}.dialog-panel{padding:24px}}@media(prefers-reduced-motion:reduce){.switch span{transition:none}}
```

코드 참조

- data-focus와 data-selected를 서로 구분해 사용하세요.

- Tailwind는 필수 전제 조건이 아닙니다.

## Headless UI 디자인 시스템 AI 에이전트 설정

### 구현 시작점

스타일과 컴포넌트 파일 전체는 위에 제공되어 있습니다. 선택한 Vite 버전에서 지원하는 Node 버전이 필요합니다. 데모는 동일한 기본 컴포넌트 패키지를 로컬에 번들링합니다.

```
# Terminal
npm create vite@latest sharing-demo -- --template react
cd sharing-demo
npm install
npm install @headlessui/react@2.2.10

# Save the complete examples as src/App.jsx and src/style.css.
# Replace src/main.jsx with:
import React from 'react';
import {createRoot} from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')).render(<App/>);

# Terminal
npm run dev
```

### 프로젝트를 위한 프롬프트

```
제공된 Headless UI App.jsx와 style.css를 사용하세요. 기본 Dialog, Listbox, Switch 구성을 유지하고, 초안 권한과 확정된 접근 권한을 분리하며, 포커스 및 선택 상태 선택자를 모두 유지하세요. 로컬에서 초대를 추가하는 부분을 대기 및 실패 상태를 포함한 승인된 API로 대체하세요. onClose에서 저장하지 마세요. 사용자 지정 CSS 팔레트를 공식 Headless UI 테마라고 설명하지 마세요.
```

## Headless UI 디자인 시스템: 출처와 범위

기본 Headless UI 2.2.10 동작을 사용하는 독자적인 OpenDesign 시각 설정 및 React 예제입니다. CSS 값은 애플리케이션에서 선택한 설정이며, Headless UI은 이 테마를 제공하지 않습니다. 초대와 권한 변경은 로컬 미리보기 메모리에만 저장됩니다.

- Dialog API 공식 상호작용, 구성 및 상태 참조

- 리스트박스 API 공식 상호작용, 구성 및 상태 참조

- 스위치 API 공식 상호작용, 구성 및 상태 참조

## Headless UI 디자인 시스템 관련 리소스

### HeroUI

실제 시맨틱 CSS 변수, 복합 React 컨트롤, 라이트 및 다크 배경 레이어, 바로 사용할 수 있는 이미지 내보내기 레시피를 제공하는 HeroUI v3 참고 자료.

### Ionic Framework

색상 역할, iOS 및 Material 슬라이더 범위의 기하 구조, 실제 세그먼트 및 스위치 동작이 검증된 네이티브 Ionic 읽기 설정 인터페이스입니다. 글꼴, 크기, 종이 설정에 따라 기사 표시도 달라집니다.

### Kobalte

Kobalte는 Solid용 헤드리스 컴포넌트를 제공합니다. 여기에는 입력값과 서식이 적용된 값이 분리된 숫자 필드, 구조가 명확한 복합 체크박스, 눌림 상태를 지원하는 버튼이 포함됩니다. 이 가이드에서는 파생 수량과 초기화 가능한 재료 선택 항목을 갖춘 레시피 배율 조정기를 통해 이러한 기본 요소를 Solid 시그널과 연결합니다.


- [구현 레퍼런스](https://headlessui.com/react/dialog)
- [출처](https://headlessui.com/react/dialog)
- [출처](https://headlessui.com/react/listbox)
- [지원 사양 읽기](https://headlessui.com/react/dialog)
- [지원 사양 읽기](https://headlessui.com/react/listbox)
- [코드 참조](https://headlessui.com/react/listbox)
- [코드 참조](https://headlessui.com/react/dialog)
- [지원 사양 읽기](https://headlessui.com/react/switch)
- [Dialog API 공식 상호작용, 구성 및 상태 참조](https://headlessui.com/react/dialog)
- [리스트박스 API 공식 상호작용, 구성 및 상태 참조](https://headlessui.com/react/listbox)
- [스위치 API 공식 상호작용, 구성 및 상태 참조](https://headlessui.com/react/switch)
