Il kit di componenti

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.

Ordini del mese
1.284+12%
Da spedire
37
12 in ritardo
Incassato
48,2k€+4%
OrdineClienteStato
1042Rossi SrlPagato
1047Bianchi & C.In attesa
1051Verdi SncScaduto
Senza CSS

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.

client/app/orders.tsx
<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" />
Pagamento rifiutato
La carta è stata respinta dalla banca.
Ordini
1.284+12%

Il bottone «Riprova» non ha un tono scritto: è di pericolo perché sta in un riquadro di pericolo.

La scala

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.

1

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.

client/style.css
: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;
}
stessa scheda, stesso bottone, dentro un contenitore con altre variabili
Ordine 1042
3 articoli · consegna giovedì
PagatoSpedito
2

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.

client/style.css
/* su :root vale per tutto il progetto; qui, per un contenitore */
.voice {
  --button-case: uppercase;
  --button-weight: 700;
  --card-radius: .25rem;
  --button-radius: 999px;
}
stessi componenti, un'altra voce: bottoni, angoli, ritmo
Ordine 1042
3 articoli · consegna giovedì
PagatoSpedito
3

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.

client/app/index.tsx
<Button variant="soft" tone="success">Salva</Button>
<Button variant="outline" size="lg" icon="plus">Nuovo</Button>
<Badge tone="warning" dot>In attesa</Badge>
il risultatoIn attesa
4

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.

client/style.css
.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; }
la scheda e le sue parti, ridisegnate
Ordine 1042
3 articoli · consegna giovedì
PagatoSpedito
Soft
5

Una sola istanza

className sulla radice e classNames sulle parti toccano un componente solo, con le utility che già conosci.

client/app/orders.tsx
<Card
  icon="package"
  title="Ordine 1042"
  classNames={{ title: "font-heading text-xl italic", icon: "rotate-6" }}
  className="border-primary/40"
/>
solo questa scheda
Ordine 1042
3 articoli · consegna giovedì
6

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.

client/app/privacy.tsx
<Button unstyled className="underline decoration-dotted underline-offset-4">
  Informativa sulla privacy
</Button>
è ancora un bottone: tastiera, focus, stato
7

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/
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»
la cascata: kit · progetto · superficie
Il kitButton, Card, Tabs…
client/_uivince su quello del kit, in tutto il progetto
client/<superficie>/uivince su entrambi, solo in quella superficie
Col backend

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

<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>
Nuovo ordine
orders.create
€

IVA esclusa

ResourcePage

<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"
/>
NomeCittàStato
Rossi SrlTorinoAttivo
Bianchi & C.BariSospeso
Verdi SncLecceAttivo
DetailPage

<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} />
Rossi Srl
Torino · cliente dal 2024
Ordini14
Ultimo ordine1042
LoginForm

<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>;
});
Accedi
Bentornato
AgentChat

<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" />
Assistente
Conosce gli ordini del progetto
Quanti ordini sono in ritardo?
orders.search
Tre: il 1042 e il 1047 aspettano il corriere, il 1051 non è ancora stato pagato.

Qui i blocchi sono disegnati con le primitive del kit: un sito di presentazione non ha un backend a cui collegarli.

E ancora

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.

L'atlante

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.