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/ordini.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 prop 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 una prop o da client/style.css.

1

Una prop

variant, 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
2

Le variabili

Il kit legge le variabili standard di shadcn — --primary, --radius, --border e le altre — e nient'altro del tema. Si scrivono in client/style.css: su :root valgono per tutto 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 */
.vetrina {
  --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
3

Il CSS della radice

Ogni componente ha una radice con un indirizzo — [data-slot="card"] — 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.

client/style.css
.bordo [data-slot="card"] {
  border-style: dashed;
  border-color: color-mix(in oklab, var(--primary) 45%, transparent);
}
.bordo [data-variant="soft"] {
  letter-spacing: .02em;
}
le schede e le varianti soft dentro il contenitore
Ordine 1042
3 articoli · consegna giovedì
PagatoSpedito
Soft
4

Il CSS delle parti

Anche ogni parte ha un indirizzo — [data-slot="card-header"], [data-slot="card-title"], [data-state="open"] — e si ridisegna allo stesso modo. Il catalogo elenca le parti di ogni componente.

client/style.css
.sala [data-slot="card-header"] {
  background: linear-gradient(100deg,
    color-mix(in oklab, var(--primary) 22%, transparent), transparent);
}
.sala [data-slot="card-title"] {
  font-family: var(--font-mono);
  letter-spacing: .08em;
  text-transform: uppercase;
}
.sala [data-slot="badge"] { border-radius: 2px; }
le parti di una scheda, ridisegnate
Ordine 1042
3 articoli · consegna giovedì
PagatoSpedito
5

Una sola istanza

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

client/app/ordini.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, 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="ordini.crea" />

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/ordini/crea.ts dichiara l'input; la pagina scrive una riga
<ActionForm action="ordini.crea" />

// o dispone i campi a mano, senza ridisegnarli
<ActionForm action="ordini.crea">
  <Field name="cliente" />
  <Field name="note" control="textarea" hint="Le vede solo il magazzino" />
</ActionForm>
Nuovo ordine
ordini.crea
€

IVA esclusa

ResourcePage

<ResourcePage title="Clienti" list="clienti.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. Le date escono nel fuso del progetto, gli importi nella sua valuta, i valori di un elenco con la loro etichetta; con una rotta di dettaglio la riga intera diventa un link. Ogni prop è un modo di dire qualcosa di diverso.

// tutto ricavato dalla tabella clienti
<ResourcePage title="Clienti" list="clienti.list" />

// oppure si dice solo quello che cambia
<ResourcePage
  title="Clienti"
  list="clienti.list"
  columns={[{ key: "nome", label: "Nome", sortable: true, primary: true }, { key: "citta" }]}
  create="clienti.apri"
  rowHref={(c) => `/clienti/${c.id}`}
/>
NomeCittàStato
Rossi SrlTorinoAttivo
Bianchi & C.BariSospeso
Verdi SncLecceAttivo
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/accedi.tsx
export default definePage(function Accedi() {
  return <AuthShell><LoginForm /></AuthShell>;
});
Accedi
Bentornato
AgentChat

<AgentChat agent="assistente" />

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/aiuto.tsx
<AgentChat agent="assistente" placeholder="Chiedi qualcosa sui tuoi ordini" />
Assistente
Conosce gli ordini del progetto
Quanti ordini sono in ritardo?
ordini.cerca
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.

Intorno ai blocchi, due piccoli attrezzi dello stack: format.money, format.number, format.date, format.datetime e format.time scrivono i valori nella lingua, nel fuso e nella valuta del progetto; useParams, useNavigate e useSearchParams leggono le rotte della tua app già tipizzate. Una pagina di dettaglio con Loaded mostra il caricamento, l'errore, il «non trovato» e lo stato vuoto senza una riga di controllo.

E ancora

Altri blocchi che si collegano con una riga

Le parti di un prodotto che tutti riscrivono, già scritte e già collegate.

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.

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.