Sistema di componenti senza interfaccia · Nuova risorsa
Sistema di progettazione Headless UI.
Headless UI offre componenti di interazione senza stile per React, destinati a finestre di dialogo, listbox e interruttori. Questa guida si concentra sulla composizione del comportamento nativo di focus, selezione e popup con CSS ordinario, usando come esempio pratico un flusso di condivisione dei documenti.
Guida OpenDesign9 capitoli27 voci di riferimentoGuida applicativa originale
01
Anteprima dell’interfaccia del sistema di progettazione sistema di design Headless UI
Apri il dialogo di condivisione, convalida un indirizzo email, scegli un’autorizzazione con la tastiera, aggiungi un invito locale e attiva o disattiva il link pubblico.
@headlessui/react 2.2.10 nativo con React 19. Tutti i colori, la tipografia, le spaziature e il layout sono CSS originali dell’esempio, non token ufficiali di Headless UI. · Riferimento per l’implementazione
02
Riferimento di progettazione del sistema di progettazione sistema di design Headless UI
Headless UI non dispone di una scala ufficiale di token visivi. Tutti i valori numerici e cromatici qui sotto appartengono a questo esempio OpenDesign; la documentazione collegata descrive i comportamenti e le API di stato. Pacchetto testato: @headlessui/react 2.2.10.
Sistema di progettazione sistema di design Headless UI: componi il comportamento prima di definire lo stile della schermata
Componi il comportamento prima di definire lo stile della schermata
Dialog, Listbox e Switch modellano ciascuno un’interazione diversa. Dialog gestisce un’attività circoscritta e il relativo contesto di focus; Listbox rappresenta una singola scelta confermata; Switch rappresenta un’impostazione booleana indipendente. Combinarli non significa unificarne lo stato: la bozza dell’invito, l’autorizzazione selezionata e l’impostazione del link pubblico restano separati.
L’esempio usa il componente React API con import diretti, come DialogPanel e ListboxButton. La finestra di dialogo contiene un listbox, quindi il posizionamento del popup e la sua chiusura devono funzionare all’interno di un overlay già presente. Parti da questa composizione prima di aggiungere il pannello del documento e il riepilogo degli accessi. Il CSS fornito è un esempio completo di aspetto per questi elementi nativi.
Anatomia di Dialog, Listbox e Switch nel sistema di progettazione sistema di design Headless UI
Definisci con attenzione i limiti della sovrapposizione
DialogBackdrop è un elemento fratello del contenitore con posizionamento fisso. DialogPanel è il pannello bianco circoscritto all’interno di tale contenitore; non occupa l’intera viewport. DialogTitle indica l’attività e Description spiega cosa accade quando si chiude. L’input nativo ha data-autofocus e Headless UI gestisce il comportamento del focus nel dialog circostante.
Una listbox seleziona un valore; uno switch modifica un valore booleano
ListboxButton mostra il ruolo selezionato. ListboxOptions usa anchor="bottom" e una larghezza basata su --button-width, così il menu a comparsa si allinea al relativo pulsante di attivazione. Ogni ListboxOption ha un valore stringa stabile. Gli attributi nativi data-focus e data-selected determinano due trattamenti CSS distinti.
Lo Switch per il link pubblico si trova in Field con Label, anziché essere un pulsante a pillola senza etichetta. Quando è selezionato, cambia lo stato del controllo accessibile e la frase esplicativa visibile. L’invito locale è indipendente dall’impostazione del link pubblico.
Sistema di design Headless UI Sistema di progettazione: separare selezione, chiusura e conferma locale
Il modello di stato utilizzato qui
All’apertura viene inizializzata una nuova bozza per il destinatario con il ruolo Can view. Annulla, Escape e un clic all’esterno chiudono la finestra di dialogo; nessuna di queste azioni aggiunge un destinatario. Al momento dell’invio, l’email viene convalidata e si rifiuta un indirizzo già presente; poi l’invito viene aggiunto alla memoria locale e la finestra si chiude. La conferma specifica chiaramente che non è stata inviata alcuna email.
Il Listbox delle autorizzazioni modifica il ruolo della bozza, non l’accesso del destinatario esistente. Lo stato del link pubblico modifica una variabile booleana separata. In questo modo si evita il comune errore di considerare ogni evento di chiusura come un salvataggio delle autorizzazioni.
Input non valido: lascia aperta la finestra di dialogo e associa l’errore all’email.
Destinatario duplicato: lascia invariato l’elenco degli accessi esistente.
Invito locale accettato: aggiorna l’elenco e comunica che il risultato è solo un’anteprima.
Sistema di design Headless UI Sistema di progettazione: gestire separatamente la selezione e la posizione da tastiera
Gestire separatamente la selezione e la posizione da tastiera
ListboxOption espone data-focus e data-selected per condizioni diverse. L’opzione con il focus riceve un tenue risalto cromatico, mentre l’autorizzazione selezionata è contrassegnata da un segno di spunta. Chi usa la tastiera può quindi passare tra le alternative senza perdere di vista la scelta esistente.
La stessa distinzione si applica fuori dal popup: il ruolo di errore appartiene al campo del destinatario non valido, mentre l’aspetto selezionato dello Switch indica l’accesso tramite link pubblico. Limita CSS allo stato del componente che descrive. Un selettore generico che colora allo stesso modo tutti gli elementi con il focus o selezionati renderebbe meno chiare queste diverse funzioni.
Sistema di design Headless UI Sistema di progettazione: rendere leggibili l’identità del documento e le autorizzazioni
Impostazioni tipografiche riproducibili
Il titolo del documento è di 28px con un’altezza della riga pari a 1.25, mentre il titolo della finestra di dialogo è di 20px con un’altezza della riga pari a 1.3. Il testo del corpo è di 16px/1.5 e usa il font di sistema della piattaforma. Le etichette dei campi e il testo di supporto sono di 14px. Questi valori esatti sono riportati in style.css: non serve ricavarli da uno screenshot.
L’email nell’elenco degli accessi usa overflow-wrap:anywhere, così l’indirizzo non può allargare la colonna. L’autorizzazione è visualizzata su una riga separata. Nella finestra di dialogo, il titolo completo del documento e l’autorizzazione selezionata sono visibili nel momento in cui viene aggiunto l’invito.
Sistema di design Headless UI Sistema di progettazione: completare il posizionamento dell’overlay e la responsività di CSS
Il layout non è nascosto dietro un prerequisito
Il foglio di stile fornito include l’intero overlay: posizionamento con inset fisso, opacità dello sfondo, z-index, larghezza massima, scorrimento verticale e padding. Sotto i 700px, i pannelli del documento si dispongono in colonna e il padding della finestra di dialogo si riduce. Non è necessario alcun processo di compilazione Tailwind.
Tieni distinto il contenitore di posizionamento da DialogPanel. Il contenitore copre la porzione disponibile del viewport e fornisce lo spazio dai bordi; il pannello definisce la superficie del contenuto con larghezza limitata. Assegna ai contenuti più lunghi della finestra una regione scorrevole, così il campo del destinatario, la scelta dell’autorizzazione e le azioni per chiudere restano accessibili anche su schermi stretti.
Sistema di design Headless UI Sistema di progettazione: usare il comportamento nativo per stato e focus
Verifica da tastiera
Apri la finestra di dialogo e verifica che il focus iniziale sia sul destinatario. Raggiungi il pulsante di attivazione delle autorizzazioni con Tab; usa i tasti freccia e Invio per scegliere un ruolo. Escape chiude il popup o la finestra di dialogo attivi in base al contesto di focus corrente del componente nativo. Dopo aver chiuso la finestra di dialogo, il focus dovrebbe tornare al pulsante di condivisione.
L’email usa aria-invalid e aria-describedby per l’errore personalizzato. Il segno di spunta in un’opzione è decorativo, perché Listbox fornisce le semantiche di selezione. Il controllo per il link pubblico ha un’etichetta visibile e la frase esplicativa rimane visibile dopo l’attivazione. L’opzione per la riduzione del movimento CSS rimuove la transizione decorativa del cursore dello Switch.
Sistema di design Headless UI: usa in modo coerente un’unica generazione di React API
Usare con coerenza una sola generazione di React API
La voce completa React monta App, il cui foglio di stile definisce la geometria dell’overlay e i selettori di stato. Nell’esempio verificato 2.2.10, DialogPanel, DialogBackdrop, ListboxButton e ListboxOptions sono importazioni dirette. Le sintassi precedenti per i componenti composti vanno migrate con attenzione, anziché mescolate a questa composizione.
ListboxOptions usa anchor="bottom" e la variabile nativa --button-width per allineare le opzioni al pulsante di attivazione. Questi dettagli fanno parte dell’integrazione, non sono decorazioni facoltative: rimuovere il contenitore di posizionamento o sostituire la larghezza con un valore fisso non correlato può compromettere un popup annidato, anche se il campo chiuso sembra corretto.
Installa @headlessui/react nel progetto React.
Salva App.jsx e style.css dagli esempi, poi sostituisci main.jsx come mostrato.
Avvia il server di sviluppo e verifica la chiusura della finestra di dialogo, la selezione delle autorizzazioni e il recupero dagli errori di email duplicata.
Sistema di design Headless UI Sistema di progettazione: esaminare un’interazione reale con le autorizzazioni
Criteri di accettazione
Una revisione utile parte da un documento e da un destinatario esistente, non da una galleria di componenti vuota. Aggiungi un invito valido con il ruolo Can comment, poi riapri la finestra e riprova con la stessa email. Verifica che la riga originale resti invariata e che il duplicato generi un errore specifico. Attiva il link pubblico e controlla la spiegazione indipendente.
L’esempio dimostra solo lo stato dell’interfaccia locale. Non include né la ricerca dei destinatari né un backend di condivisione. Per collegare un servizio reale occorre un modello di risposta con stati di attesa ed errore, oltre a una policy esplicita per le modifiche simultanee alle autorizzazioni; le primitive native non definiscono le regole di autorizzazione.
Sistema di design Headless UI Esempi di componenti del sistema di progettazione
App.jsx — composizione nativa per la condivisione
Dialog controllato completo, Listbox delle autorizzazioni, Switch con etichetta e convalida locale.
jsx
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:'[email protected]',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='[email protected]'/>{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>;
}
Non viene inviata alcuna email né effettuata alcuna richiesta.
Escape e Annulla eliminano l’invito non salvato.
style.css — sistema visivo originale completo
Tutti i token visivi, i selettori di stato, il posizionamento dell’overlay e le regole responsive richiesti da App.jsx. Sono scelte esemplificative, non impostazioni predefinite ufficiali di Headless UI.
Sistema di design Headless UI Configurazione dell’agente
Punto di partenza per l’implementazione
I file completi dei componenti e degli stili sono forniti sopra. È richiesta la versione di Node supportata dalla versione di Vite selezionata. La demo include localmente lo stesso pacchetto di componenti nativi.
testo
# Terminal
npm create vite@latest sharing-demo -- --template react
cd sharing-demo
npm install
npm install @headlessui/[email protected]
# 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
Un prompt per il tuo progetto
Prompt per l’agente
Usa App.jsx e style.css di Headless UI forniti. Mantieni la composizione nativa di Dialog, Listbox e Switch, separa le autorizzazioni in bozza dagli accessi confermati e conserva tutti i selettori per il focus e lo stato selezionato. Sostituisci l’aggiunta locale dell’invito con il nostro API autorizzato, includendo gli stati di attesa e di errore; non salvare mai in onClose. Non descrivere la palette personalizzata di CSS come tema ufficiale di Headless UI.
05
Sistema di design Headless UI Fonti e ambito del sistema di progettazione
Configurazione visiva originale di OpenDesign ed esempio di React che usa il comportamento nativo di Headless UI 2.2.10. I valori di CSS sono esplicitamente scelte dell’applicazione; Headless UI non fornisce questo tema. Gli inviti e le modifiche alle autorizzazioni esistono solo nella memoria locale dell’anteprima.