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 il tema del progetto — colori, carattere, raggio, densità, movimento — 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 variant="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.

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

I predefiniti del tema

Il valore che una prop ha quando la pagina non lo scrive si cambia per tutto il progetto, una volta, nel theme.json. Le pagine restano come sono.

client/theme.json
{
  "components": {
    "Tabs": { "variant": "line" },
    "Button": { "pill": true }
  }
}
prima: schede a pastiglia · dopo: sottolineate
3

Un token

Ogni componente legge i propri token (--sp-card-pad, --sp-button-h…) con un ripiego. Si cambiano su :root per tutto il sito o su un contenitore per quello che ha dentro.

client/style.css
.vetrina {
  --sp-card-pad: 1.75rem;
  --sp-card-radius: 1.75rem;
  --sp-button-h: 3rem;
  --sp-button-px: 1.6rem;
}
stessa scheda, stesso bottone, su un contenitore con i token
Ordine 1042
3 articoli · consegna giovedì
PagatoSpedito
4

Il CSS delle parti

Ogni parte di un componente ha un indirizzo — [data-slot="card-header"], [data-variant="soft"], [data-state="open"] — e il CSS del kit pesa zero: una regola tua vince sempre, senza !important. 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(--sp-primary) 22%, transparent), transparent);
}
.sala [data-slot="card-title"] {
  font-family: var(--sp-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/actions/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 … create="clienti.create" />

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; su un telefono le righe diventano schede. La lettura resta della pagina, la scrittura passa dalle azioni che nomini e un permesso che manca toglie il comando invece di spegnerlo.

const lista = useApiState("clienti.list", { page: pagina });

<ResourcePage
  title="Clienti"
  rows={lista.data?.items ?? []}
  total={lista.data?.total}
  columns={[{ key: "nome", label: "Nome", sortable: true }, { key: "citta" }, { key: "stato" }]}
  create="clienti.create"
  update="clienti.update"
  remove="clienti.remove"
  exportCsv
/>
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.

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.
PayButton
Un pagamento con la carta che passa dal tuo conto, 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.