Componenti veri. Tuoi fino all'ultima parte.
Tutto quello che vedi in questa pagina — i bottoni, le schede, le tabelle, la barra qui accanto — è fatto con i componenti che l'agente usa per la tua app. Si usano senza scrivere CSS, si piegano a sette livelli e i blocchi grandi parlano col backend da soli.
| Ordine | Cliente | Stato |
|---|---|---|
| 1042 | Rossi Srl | Pagato |
| 1047 | Bianchi & C. | In attesa |
| 1051 | Verdi Snc | Scaduto |
Una riga, e il componente è finito
Non c'è un foglio di stile da scrivere per avere una schermata che regge: i componenti leggono le variabili CSS del progetto — colori, carattere, raggio — e portano da soli lo stato, il focus, il chiaro e lo scuro. Il tono di un riquadro lo prendono anche i bottoni che ci stanno dentro.
<Alert tone="danger" title="Pagamento rifiutato"
actions={<Button size="sm">Riprova</Button>}>
La carta è stata respinta dalla banca.
</Alert>
<Stat layout="card" label="Ordini" value="1.284"
delta="+12%" trend="up" icon="shopping-bag" />Il bottone «Riprova» non ha un tono scritto: è di pericolo perché sta in un riquadro di pericolo.
Sette livelli, dal valore di una variabile a un tuo file
Ci si ferma dove basta. Ogni livello arriva un po' più in là del precedente e costa un po' di più; i primi quattro non toccano il codice di nessun componente e passano da client/style.css o da una prop.
Le variabili del colore
Dieci valori — --primary, --radius, --border e le altre variabili standard di shadcn — e tutto il resto del tema ne discende. Su :root valgono per il progetto, su un contenitore per quello che ha dentro; il chiaro e lo scuro sono due blocchi, :root e .dark.
:root {
--primary: oklch(0.55 0.2 275);
--radius: 0.5rem;
}
.dark {
--primary: oklch(0.7 0.18 275);
}
/* o solo per quello che sta dentro un contenitore */
.palette {
--primary: oklch(0.62 0.19 150);
--radius: 1.75rem;
}Le variabili della voce
Il carattere dei titoli, la forma dei bottoni, il ritmo delle fasce, l'entrata delle sezioni: variabili come --button-case, --display-size, --rhythm, --reveal-travel, a livello di progetto o di un solo contenitore.
/* su :root vale per tutto il progetto; qui, per un contenitore */
.voice {
--button-case: uppercase;
--button-weight: 700;
--card-radius: .25rem;
--button-radius: 999px;
}Una prop
variant dice il riempimento, layout la struttura, tone e size hanno gli stessi valori in tutto il kit: imparati su un componente, valgono per tutti.
<Button variant="soft" tone="success">Salva</Button>
<Button variant="outline" size="lg" icon="plus">Nuovo</Button>
<Badge tone="warning" dot>In attesa</Badge>Il CSS della radice e delle parti
Ogni componente ha una radice con un indirizzo — [data-slot="card"] —, ogni parte il suo — [data-slot="card-header"] — e ogni variante un attributo — [data-variant="soft"]. Il CSS del kit pesa zero e sta in un livello sotto il tuo: una regola scritta da te vince sempre, senza !important. Il catalogo elenca le parti di ogni componente.
.dashed [data-slot="card"] {
border-style: dashed;
}
.dashed [data-slot="card-title"] {
font-family: var(--font-mono);
letter-spacing: .08em;
text-transform: uppercase;
}
.dashed [data-slot="badge"] { border-radius: 2px; }
.dashed [data-variant="soft"] { letter-spacing: .02em; }Una sola istanza
className sulla radice e classNames sulle parti toccano un componente solo, con le utility che già conosci.
<Card
icon="package"
title="Ordine 1042"
classNames={{ title: "font-heading text-xl italic", icon: "rotate-6" }}
className="border-primary/40"
/>Senza lo stile del kit
unstyled toglie tutto il CSS del kit da quell'istanza e dalle sue parti. Resta il comportamento: l'accessibilità, i gesti, gli stati.
<Button unstyled className="underline decoration-dotted underline-offset-4">
Informativa sulla privacy
</Button>Un tuo file al posto del suo
Un file con lo stesso nome di un componente del kit lo sostituisce — il .tsx e il .css —, e tutti gli altri componenti che lo usano usano il tuo. Per ciò che il kit non prevede, i componenti del progetto vivono nella stessa cartella.
client/_ui/button.tsx il tuo Button, in tutte le superfici
client/_ui/button.css e il suo aspetto
client/app/ui/card.tsx la tua Card, solo nella superficie «app»I blocchi grandi parlano col progetto da soli
Un modulo, una tabella di gestione, la pagina di un record, l'accesso, la chat con un agente: nella tua app sono una riga, perché sanno già con quale azione, tabella o agente del progetto parlare.
<ActionForm action="orders.create" />
Un campo per ogni input dell'azione, col controllo giusto per il suo tipo, i vincoli, gli errori del server messi sotto il campo che li ha causati e il messaggio di esito. Cambi l'azione e il modulo cambia.
// server/orders/create.ts dichiara l'input; la pagina scrive una riga
<ActionForm action="orders.create" />
// o dispone i campi a mano, senza ridisegnarli
<ActionForm action="orders.create">
<Field name="customer" />
<Field name="notes" control="textarea" hint="Le vede solo il magazzino" />
</ActionForm>IVA esclusa
<ResourcePage title="Clienti" list="customers.list" />
La schermata di gestione intera di una tabella: testata, filtri, elenco, creazione, modifica e cancellazione con il modulo generato dalle azioni, lo stato vuoto e quello d'errore, la paginazione. Colonne, filtri e operazioni si ricavano dalla tabella e da cosa la persona può fare: un permesso che manca toglie il comando invece di spegnerlo. Date nel fuso del progetto, importi nella sua valuta, valori di un elenco con la loro etichetta. Ogni prop è un modo di dire qualcosa di diverso.
// tutto ricavato dalla tabella customers
<ResourcePage title="Clienti" list="customers.list" />
// oppure si dice solo quello che cambia
<ResourcePage
title="Clienti"
list="customers.list"
columns={[{ key: "name", label: "Nome", sortable: true, primary: true }, { key: "city" }]}
create="customers.open"
/>| Nome | Città | Stato |
|---|---|---|
| Rossi Srl | Torino | Attivo |
| Bianchi & C. | Bari | Sospeso |
| Verdi Snc | Lecce | Attivo |
<DetailPage get="customers.get" params={{ id }} />
La pagina di un record: legge da sola, e il caricamento, l'errore, il «non trovato» e lo stato vuoto sono già disegnati. Con una rotta di dettaglio, la riga di un elenco diventa un link alla sua pagina.
// client/app/customers/[id].tsx
const { id } = useParams();
<DetailPage get="customers.get" params={{ id }} title={(c) => c.name} /><LoginForm />
L'accesso del progetto: i metodi che la superficie offre — password, codice via email, account Google — la registrazione, il recupero, il secondo fattore. Quello che cambia fra un progetto e l'altro è nel manifest della superficie, non nella pagina.
// client/app/sign-in.tsx
export default definePage(function SignIn() {
return <AuthShell><LoginForm /></AuthShell>;
});<AgentChat agent="assistant" />
La conversazione con un agente del progetto: risposte che arrivano mentre vengono scritte, gli strumenti che l'agente usa mostrati riga per riga, le conversazioni salvate. L'agente lo descrivi come descrivi il resto; la chat c'è già.
// client/app/help.tsx
<AgentChat agent="assistant" placeholder="Chiedi qualcosa sui tuoi ordini" />Qui i blocchi sono disegnati con le primitive del kit: un sito di presentazione non ha un backend a cui collegarli.
Altri blocchi e attrezzi che si collegano con una riga
Le parti di un prodotto che tutti riscrivono, già scritte e già collegate.
Griglie che si stringono
Una Grid o una KpiGrid da quattro colonne passa a due e a una, mai a una riga con una tessera sola: il numero di colonne segue la larghezza del suo riquadro, non della finestra.
Account
Dati, password, social collegati, secondi fattori e dispositivi: la pagina delle impostazioni di una persona.
Pay
Un pagamento con carta: porta il cliente alla pagina di pagamento o la apre dentro la tua, i dati della carta non passano mai dalla pagina, e il progetto ne riceve l'esito.
PushToggle
L'iscrizione alle notifiche di chi usa la tua app, sul telefono e nel browser.
ConnectButton
Il collegamento a un servizio esterno — GitHub, il tuo CRM — con il consenso di chi lo concede.
Valori nella tua lingua
format.money, format.number, format.date e gli altri scrivono i valori nella lingua, nel fuso e nella valuta del progetto; useParams e useSearchParams leggono le rotte già tipizzate.
Ogni componente, in ogni variante
Una pagina sola che li mostra tutti nello stato in cui li trova la tua app, con le varianti, i toni e le parti che il catalogo dichiara. È lo stesso kit di questo sito.
Descrivi l'app. I componenti ci sono già.
L'agente li usa per te, e quando vuoi andare più a fondo sai a quale livello fermarti.