# Sistema de design Headless UI

## Prévia da interface do sistema de design Headless UI

Abra a caixa de diálogo de compartilhamento, valide um e-mail, escolha uma permissão pelo teclado, adicione um convite local e alterne o estado do link público.

## Referência de design do sistema de design Headless UI

Headless UI não tem uma escala oficial de tokens visuais. Cada valor numérico ou de cor abaixo pertence a este exemplo de OpenDesign; a documentação vinculada descreve o comportamento e as APIs de estado. Pacote testado: @headlessui/react 2.2.10.

### Papéis de cor originais de CSS

| Nome / Regra | Valor / Configuração | Uso |
| --- | --- | --- |
| --canvas | #f5f5f4 | Área de trabalho do documento. |
| --surface | #ffffff | Papel, painel de acesso e caixa de diálogo. |
| --ink | #1c1917 | Texto principal. |
| --muted | #57534e | Texto secundário. |
| --line | #d6d3d1 | Bordas do painel e dos campos de entrada. |
| --accent | #4338ca | Ação principal e contorno de foco. |
| --accent-soft | #eef2ff | Opção em foco e fundo do avatar. |
| --error | #b91c1c | Texto e borda de e-mail inválido. |
### Espaçamento e geometria originais

| Nome / Regra | Valor / Configuração | Uso |
| --- | --- | --- |
| --space-1 / --space-2 | 4px / 8px | Pequenos incrementos. |
| --space-3 / --space-4 | 12px / 16px | Espaçamento entre controles e recuo em dispositivos móveis. |
| --space-6 / --space-8 | 24px / 32px | Preenchimento do painel e separação principal. |
| --radius-control | 8px | Botões e campos de entrada. |
| --radius-panel | 16px | Papel e caixa de diálogo. |
| .dialog-panel width | min(100%, 460px) | Sobreposição delimitada em telas estreitas. |
| .dialog-panel max-height | calc(100% - 16px) | Mantém espaço dentro do contêiner de posicionamento fixo. |
### Tipografia e escolhas responsivas originais

| Nome / Regra | Valor / Configuração | Uso |
| --- | --- | --- |
| Body | 16px / 1.5 system-ui | Não é necessária uma fonte externa. |
| Screen title | 28px / 1.25 | Identidade do documento. |
| Document headline | 32px; 26px below 700px | Hierarquia editorial do conteúdo. |
| Dialog heading | 20px / 1.3 | Identifica a tarefa de compartilhamento. |
| Labels and helper copy | 14px | Contexto do campo. |
| Error and access description | 13px | Feedback persistente. |
| Breakpoint | 700px | Os painéis do documento e de acesso ficam empilhados. |
### Contratos de interação nativos

| Nome / Regra | Valor / Configuração | Uso |
| --- | --- | --- |
| Dialog open / onClose | Controlled boolean / dismissal callback | A tecla Escape e a interação fora da caixa solicitam o fechamento; não salvam nada. |
| DialogPanel | Content boundary | O fundo escurecido e o contêiner de posicionamento ficam fora deste limite. |
| Listbox value / onChange | Controlled permission string | A navegação pelo teclado atualiza a permissão selecionada. |
| data-focus / data-selected | Native state attributes | CSS separa o foco do teclado da seleção confirmada. |
| Switch checked / onChange | Controlled boolean | data-checked define o estilo do estado visual. |
## Guia completo do sistema de design Headless UI

Um guia prático para escolher, aplicar e avaliar este sistema em uma interface real.

Orientações completas, um ponto de partida para a implementação e links para o código-fonte.

## Sistema de design Headless UI: componha o comportamento antes de estilizar a tela

### Componha o comportamento antes de estilizar a tela

> Dialog, Listbox e Switch representam interações diferentes. Dialog controla uma tarefa delimitada e seu contexto de foco; Listbox representa uma única opção confirmada; Switch representa uma configuração booleana independente. Combiná-los não exige combinar seus estados: o rascunho do convite, a permissão selecionada e a configuração do link público permanecem separados.

O exemplo usa o componente React API com importações diretas, como DialogPanel e ListboxButton. A caixa de diálogo contém uma caixa de listagem, então o posicionamento do menu e o fechamento precisam funcionar dentro de uma sobreposição existente. Comece por essa composição antes de adicionar ao redor dela o painel do documento e o resumo de acesso. O CSS fornecido apresenta uma aparência completa para essas partes nativas.

## Sistema de design Headless UI: estrutura de Dialog, Listbox e Switch

### Defina limites intencionais para a sobreposição

DialogBackdrop é irmão do contêiner de posicionamento fixo. DialogPanel é o painel branco delimitado dentro desse contêiner; ele não ocupa toda a janela de visualização. DialogTitle identifica a tarefa, e Description explica o que acontece ao fechar. O campo de entrada nativo tem data-autofocus, e Headless UI gerencia o comportamento de foco da caixa de diálogo ao redor.

Consulte a especificação de apoio

### A caixa de listagem seleciona um valor; o interruptor altera um valor booleano

ListboxButton mostra a função selecionada. ListboxOptions usa anchor="bottom" e uma largura baseada em --button-width, para que o menu fique alinhado ao botão que o aciona. Cada ListboxOption tem um valor de texto estável. Os atributos nativos data-focus e data-selected controlam dois estilos CSS distintos.

O Switch do link público fica em Field com Label, em vez de ser um botão decorativo sem rótulo. Seu estado marcado altera o controle acessível e a frase explicativa visível. O convite local é independente da configuração do link público.

Consulte a especificação de apoio

## Sistema de design Headless UI: separe seleção, fechamento e confirmação local

### O modelo de estado usado aqui

Ao abrir, um novo rascunho de destinatário é iniciado com a função Can view. Cancelar, pressionar Escape ou clicar fora fecha a caixa de diálogo; nenhuma dessas ações adiciona um destinatário. Ao enviar, o e-mail é validado e, se o endereço já estiver presente, é rejeitado. Em seguida, o convite é adicionado à memória e a caixa de diálogo é fechada. A confirmação informa explicitamente que nenhum e-mail foi enviado.

A caixa de listagem de permissões altera a função do rascunho, não o acesso de um destinatário existente. O estado do link público altera um valor booleano separado. Assim, evita-se o erro comum de tratar cada evento de fechamento como persistência de permissão.

- Entrada inválida: mantenha a caixa de diálogo aberta e associe o erro ao e-mail.

- Destinatário duplicado: mantenha inalterada a lista de acessos existente.

- Convite local aceito: atualize a lista e anuncie o resultado, disponível apenas na prévia.

## Sistema de design Headless UI: estilize a seleção e o foco do teclado de forma independente

### Estilize a seleção e o foco do teclado de forma independente

ListboxOption expõe data-focus e data-selected para condições diferentes. A opção em foco recebe o tratamento de destaque claro, enquanto a permissão confirmada recebe uma marca de seleção. Assim, quem usa o teclado pode percorrer as alternativas sem perder de vista a opção atual.

A mesma distinção se aplica fora do menu: o papel de erro pertence ao campo de destinatário inválido, enquanto a aparência marcada do Switch corresponde ao acesso pelo link público. Restrinja CSS ao estado do componente que ele descreve. Um seletor amplo que aplicasse a mesma cor a todos os elementos em foco ou marcados ocultaria esses significados diferentes.

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

Referência de código

## Sistema de design Headless UI: torne a identidade do documento e as permissões fáceis de entender

### Configurações tipográficas reproduzíveis

O título do documento usa 28px com altura de linha de 1.25, enquanto o título da caixa de diálogo usa 20px com altura de linha de 1.3. O texto do corpo usa 16px/1.5 na fonte de sistema da plataforma. Os rótulos dos campos e o texto auxiliar usam 14px. Esses valores exatos estão em style.css; não é necessário inferi-los de uma captura de tela.

O endereço de e-mail na lista de acessos usa overflow-wrap:anywhere para impedir que ele force a coluna a ficar mais larga. A permissão permanece em uma linha separada. Dentro da caixa de diálogo, o título completo do documento e a permissão selecionada ficam visíveis no momento em que o convite é adicionado.

## Sistema de design Headless UI: posicionamento completo da sobreposição e CSS responsivo

### O layout não fica oculto em um pré-requisito

A folha de estilos fornecida inclui toda a sobreposição: posicionamento fixo com inset, opacidade do fundo, z-index, largura máxima, rolagem vertical e espaçamento interno. Abaixo de 700px, os painéis do documento ficam empilhados e o diálogo reduz o espaçamento interno. Não se pressupõe uma compilação do Tailwind.

Mantenha o contêiner de posicionamento separado de DialogPanel. O contêiner ocupa a área disponível da janela de visualização e fornece espaçamento nas bordas; o painel fornece uma superfície de conteúdo com limites definidos. Para diálogos com conteúdo mais longo, crie uma área rolável para que o campo de destinatário, a escolha de permissão e as ações de fechar continuem acessíveis em larguras estreitas.

```
.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; }
```

Referência de código

## Use o estado e o comportamento de foco nativos do sistema de design Headless UI

### Verificação pelo teclado

Abra o diálogo e confirme que o foco inicial está no destinatário. Pressione Tab para chegar ao acionador de permissão; use as teclas de seta e Enter para escolher uma função. Escape fecha o popup ou o diálogo ativo, de acordo com o contexto de foco atual do componente nativo. Depois de fechar o diálogo, o foco deve retornar ao acionador de compartilhamento.

O e-mail usa aria-invalid e aria-describedby para exibir um erro personalizado. A marca de seleção em uma opção é decorativa, pois o Listbox fornece a semântica de seleção. O controle de link público tem um rótulo visível, e a frase explicativa permanece após a alternância. A configuração de movimento reduzido CSS remove a transição decorativa do botão deslizante.

Consulte a especificação de apoio

## Use uma única geração de React API de forma consistente no sistema de design Headless UI

### Use uma única geração de React API de forma consistente

A entrada completa React monta App, cuja folha de estilos fornece a geometria da sobreposição e os seletores de estado. DialogPanel, DialogBackdrop, ListboxButton e ListboxOptions são importações diretas no exemplo verificado da versão 2.2.10. As grafias antigas de componentes compostos devem ser migradas deliberadamente, em vez de misturadas a esta composição.

ListboxOptions usa anchor="bottom" e a variável nativa --button-width para alinhar as opções ao acionador. Esses detalhes fazem parte da integração, não são enfeites opcionais: remover o contêiner de posicionamento ou substituir a largura por um valor fixo sem relação com o acionador pode comprometer um popup aninhado, mesmo que o campo fechado ainda pareça correto.

1. Instale @headlessui/react no projeto React.

2. Salve App.jsx e style.css a partir dos exemplos e, em seguida, substitua main.jsx conforme mostrado.

3. Inicie o servidor de desenvolvimento e teste o fechamento do diálogo, a seleção de permissões e a recuperação após e-mails duplicados.

## Inspecione uma interação real de permissão no sistema de design Headless UI

### Critérios de aceitação

Uma revisão útil começa com um documento e um destinatário existente, não com uma galeria de componentes vazia. Adicione um convite válido com a permissão Pode comentar e, em seguida, reabra o diálogo e tente usar o mesmo e-mail. Confirme que a linha original é mantida e que a duplicata gera um erro específico. Ative o link público e confira a explicação correspondente.

O exemplo demonstra apenas o estado local da interface. Ele não oferece busca de destinatários nem back-end de compartilhamento. Conectar um serviço real exige um modelo de respostas para estados pendentes e de erro, além de uma política explícita para alterações simultâneas de permissões; os elementos primitivos nativos não definem regras de autorização.

## Exemplos de componentes do sistema de design Headless UI

### App.jsx — composição de compartilhamento nativa

Dialog controlado completo, Listbox de permissões, Switch com rótulo e validação local.

```
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>;
}
```

Referência de código

- Nenhum e-mail é enviado, nem qualquer solicitação é feita.

- Escape e Cancelar descartam o convite não salvo.

### style.css — sistema visual original completo

Todos os tokens visuais, seletores de estado, posicionamento da sobreposição e regras responsivas exigidos por App.jsx. Estas são escolhas do exemplo, não configurações padrão oficiais de 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}}
```

Referência de código

- Mantenha data-focus e data-selected distintos.

- Não é necessário usar Tailwind.

## Configuração do agente do sistema de design Headless UI

### Ponto de partida para implementação

Os arquivos completos de estilos e componentes estão acima. Requer a versão do Node compatível com a versão selecionada do Vite. A demonstração inclui o mesmo pacote de componentes nativos localmente.

```
# 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
```

### Um prompt para seu projeto

```
Use App.jsx e style.css de Headless UI fornecidos. Preserve a composição nativa de Dialog, Listbox e Switch, mantenha as permissões provisórias separadas do acesso confirmado e preserve todos os seletores de foco e de estado selecionado. Substitua a inclusão local de convites pela nossa API autorizada, incluindo estados pendentes e de falha; nunca salve em onClose. Não descreva a paleta personalizada de CSS como um tema oficial de Headless UI.
```

## Fontes e escopo do sistema de design Headless UI

Configuração visual original de OpenDesign e exemplo de React usando o comportamento nativo de Headless UI 2.2.10. Os valores de CSS são explicitamente escolhas do aplicativo; Headless UI não oferece este tema. Os convites e as alterações de permissões existem apenas na memória da prévia local.

- Dialog API Referência oficial de interação, composição e estado.

- Listbox API Referência oficial de interação, composição e estado.

- Switch API Referência oficial de interação, composição e estado.

## Recursos relacionados ao sistema de design Headless UI

### HeroUI

Uma referência de HeroUI v3 com variáveis CSS semânticas reais, controles compostos de React, superfícies claras e escuras e um procedimento funcional de exportação de imagens.

### Ionic Framework

Uma interface nativa do Ionic para preferências de leitura, com funções de cor verificadas, geometria de controles deslizantes para iOS e Material, comportamento real de segmentos e interruptores e um artigo que se adapta às escolhas de fonte, tamanho e papel.

### Kobalte

Kobalte fornece componentes sem interface visual para Solid, incluindo campos numéricos com valores brutos e formatados separados, a anatomia composta de caixas de seleção e botões com estado pressionado. Este guia conecta esses primitivos aos sinais do Solid por meio de um ajustador de receitas com quantidades derivadas e caixas de seleção de ingredientes que podem ser redefinidas.


- [Referência de implementação](https://headlessui.com/react/dialog)
- [Fonte](https://headlessui.com/react/dialog)
- [Fonte](https://headlessui.com/react/listbox)
- [Consulte a especificação de apoio](https://headlessui.com/react/dialog)
- [Consulte a especificação de apoio](https://headlessui.com/react/listbox)
- [Referência de código](https://headlessui.com/react/listbox)
- [Referência de código](https://headlessui.com/react/dialog)
- [Consulte a especificação de apoio](https://headlessui.com/react/switch)
- [Dialog API Referência oficial de interação, composição e estado.](https://headlessui.com/react/dialog)
- [Listbox API Referência oficial de interação, composição e estado.](https://headlessui.com/react/listbox)
- [Switch API Referência oficial de interação, composição e estado.](https://headlessui.com/react/switch)
