<button type="button" class="rtds-btn 
    rtds-btn--primary
    ">

    Button label

    <svg class="rtds-icon rtds-fill-current rtds-w-4 rtds-h-4 rtds-animate-spin" aria-hidden="true" focusable="false" role="img">
        <use href="../../icons.svg#spinner" />
    </svg>

    <span class="rtds-sr-only">Caricamento...</span>
</button>
{% extends '@button' %}

{%- block after -%}
    {% render '@icon--small', { id: 'spinner', classes: 'rtds-animate-spin' }, true %}
    <span class="rtds-sr-only">Caricamento...</span>
{%- endblock after -%}
{
  "label": "Button label",
  "iconSize": "rtds-w-4 rtds-h-4 md:rtds-w-5 md:rtds-h-5 lg:rtds-w-6 lg:rtds-h-6",
  "iconTransformType": "translate"
}
  • Content:
    /**
     * BUTTON
     *
    */
    .rtds-btn {
        font-size: var(--fontsize-text-lg);
        letter-spacing: 0.025em;
        transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
        transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
        display: inline-flex;
        justify-content: center;
        align-items: center;
        gap: var(--spacing-1);
        padding-left: var(--spacing-4);
        padding-right: var(--spacing-4);
        padding-top: var(--spacing-3);
        padding-bottom: var(--spacing-3);
        border-radius: 0.375rem;
        line-height: 1;
        font-weight: 700;
        transition-property: all;
        transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
        transition-duration: 200ms;
        transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
        border-width: 1px;
    }
    .rtds-btn--primary {
            background-color: var(--color-button-primary);
            border-color: var(--color-button-primary);
        --tw-text-opacity: 1;
        color: rgb(255 255 255 / var(--tw-text-opacity, 1));
    }
    .rtds-btn--primary:hover {
        --tw-text-opacity: 1;
        color: rgb(255 255 255 / var(--tw-text-opacity, 1));
            background-color: var(--color-button-primary-hover);
            border-color: var(--color-button-primary-hover);
    }
    .rtds-btn--primary:focus {
            background-color: var(--color-button-primary-hover);
            border-color: var(--color-button-primary-hover);
        }
    .rtds-btn--primary:active {
            background-color: var(--color-button-primary-active);
            border-color: var(--color-button-primary-active);
        }
    .rtds-btn--secondary {
            background-color: var(--color-button-secondary);
            border-color: var(--color-button-secondary);
        --tw-text-opacity: 1;
        color: rgb(255 255 255 / var(--tw-text-opacity, 1));
    }
    .rtds-btn--secondary:hover {
            background-color: var(--color-button-secondary-hover);
            border-color: var(--color-button-secondary-hover);
        }
    .rtds-btn--secondary:focus {
            background-color: var(--color-button-secondary-hover);
            border-color: var(--color-button-secondary-hover);
        }
    .rtds-btn--secondary:active {
            background-color: var(--color-button-secondary-active);
            border-color: var(--color-button-secondary-active);
        }
    .rtds-btn--only-text {
        background-color: transparent;
            color: var(--color-button-only-text);
            border-color: transparent;
    }
    .rtds-btn--only-text:hover {
            color: var(--color-button-only-text-hover);
            border-color: transparent;
            background-color: var(--color-background-02);
        }
    .rtds-btn--only-text:focus {
            color: var(--color-button-only-text-hover);
            border-color: transparent;
            background-color: var(--color-background-01);
        }
    .rtds-btn--only-text:active {
            color: var(--color-button-only-text-active);
            border-color: transparent;
        }
    .rtds-btn--outlined {
        border-color: currentColor;
        background-color: transparent;
            color: var(--color-button-outline);
            border-color: var(--color-button-outline);
        border-width: 1px;
    }
    .rtds-btn--outlined:hover {
        --tw-text-opacity: 1;
        color: rgb(255 255 255 / var(--tw-text-opacity, 1));
            background-color: var(--color-button-primary);
            border-color: var(--color-button-primary);
    }
    .rtds-btn--outlined:focus {
            color: var(--color-button-outline-hover);
            border-color: var(--color-button-outline-hover);
            background-color: var(--color-background-01);
        }
    .rtds-btn--inverted {
            background-color: var(--color-button-inverted);
            border-color: var(--color-button-inverted);
            color: var(--color-button-inverted-text);
        }
    .rtds-btn--inverted:hover {
            background-color: var(--color-button-inverted-hover);
            border-color: var(--color-button-inverted-hover);
            color: var(--color-button-inverted-text-hover);
        }
    .rtds-btn--inverted:focus {
            background-color: var(--color-button-inverted-focus);
            border-color: var(--color-button-inverted-focus);
            color: var(--color-button-inverted-text-focus);
        }
    .rtds-btn--inverted:active {
            background-color: var(--color-button-inverted-active);
            border-color: var(--color-button-inverted-active);
            color: var(--color-button-inverted-text-active);
        }
    .rtds-btn--text-inverted {
            background-color: var(--color-button-text-inverted);
            border-color: var(--color-button-text-inverted);
            color: var(--color-button-text-inverted-text);
        }
    .rtds-btn--text-inverted:hover {
            background-color: var(--color-button-text-inverted-hover);
            border-color: var(--color-button-text-inverted-hover);
            color: var(--color-button-text-inverted-text-hover);
        }
    .rtds-btn--text-inverted:focus {
            background-color: var(--color-button-text-inverted-focus);
            border-color: var(--color-button-text-inverted-focus);
            color: var(--color-button-text-inverted-text-focus);
        }
    .rtds-btn--text-inverted:active {
            background-color: var(--color-button-text-inverted-active);
            border-color: var(--color-button-text-inverted-active);
            color: var(--color-button-text-inverted-text-active);
        }
    /* UTILITY VARIANTS FOR SIZE MANAGING AND ICON MANAGING */
    .rtds-btn--icon {
        height: var(--spacing-9);
        width: var(--spacing-9);
        border-radius: 9999px;
        padding: var(--spacing-0);
        justify-content: center;
    }
    .rtds-btn--icon-square {
        height: var(--spacing-9);
        width: var(--spacing-9);
        padding: var(--spacing-0);
        justify-content: center;
        border-radius: 0.375rem;
    }
    /*.rtds-btn--icon-right,
        .rtds-btn--icon-left {
            @apply rtds-justify-between;
        }*/
    .rtds-btn--icon-right {
        padding-right: var(--spacing-3);
    }
    .rtds-btn--icon-left {
        padding-left: var(--spacing-3);
    }
    .rtds-btn--icon-right .rtds-icon,
        .rtds-btn--icon-left .rtds-icon {
        transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
        transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
        transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
        transition-property: all;
        transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
        transition-duration: 200ms;
        transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
        flex: none;
    }
    /* SIZE MANAGEMENT */
    .rtds-btn--s {
        gap: var(--spacing-0-5);
        font-size: var(--fontsize-text-sm);
    }
    .rtds-btn--l {
        padding-left: var(--spacing-6);
        padding-right: var(--spacing-6);
        font-size: var(--fontsize-text-xl);
        gap: var(--spacing-2);
        padding-top: var(--spacing-3);
        padding-bottom: var(--spacing-3);
    }
    /* SPACING MANAGEMENT FOR BUTTON WITH ICON */
    .rtds-btn--s:where(.rtds-btn--icon),
        .rtds-btn--s:where(.rtds-btn--icon-square) {
        width: var(--spacing-6);
        height: var(--spacing-6);
    }
    .rtds-btn--s:where(.rtds-btn--icon-right),
        .rtds-btn--s:where(.rtds-btn--icon-left) {
        gap: var(--spacing-1-5);
    }
    .rtds-btn--s:where(.rtds-btn--icon-right) {
        padding-right: var(--spacing-3);
    }
    .rtds-btn--s:where(.rtds-btn--icon-left) {
        padding-left: var(--spacing-3);
    }
    .rtds-btn--l:where(.rtds-btn--icon),
        .rtds-btn--l:where(.rtds-btn--icon-square) {
        height: var(--spacing-12);
        width: var(--spacing-12);
    }
    .rtds-btn--l:where(.rtds-btn--icon-right) {
        padding-right: var(--spacing-4);
    }
    .rtds-btn--l:where(.rtds-btn--icon-left) {
        padding-left: var(--spacing-4);
    }
    .rtds-btn--s:where(.rtds-btn--icon),
        .rtds-btn--l:where(.rtds-btn--icon),
        .rtds-btn--s:where(.rtds-btn--icon-square),
        .rtds-btn--l:where(.rtds-btn--icon-square) {
        padding-left: var(--spacing-0);
        padding-right: var(--spacing-0);
    }
    /* DISABLED STATE */
    .rtds-btn[disabled] {
            background-color: var(--color-background-02);
            color: var(--color-button-disabled);
            border-color: var(--color-background-02);
            cursor: not-allowed;
        }
    .rtds-btn[aria-disabled="true"] {
            background-color: var(--color-background-02);
            color: var(--color-button-disabled);
            border-color: var(--color-background-02);
            cursor: not-allowed;
        }
    
  • URL: /components/raw/button/button.built.css
  • Filesystem Path: components/02-atoms/button/button.built.css
  • Size: 8.2 KB
  • Content:
    /**
     * BUTTON
     *
    */
    @layer components {
        .rtds-btn {
            @apply rtds-py-3 rtds-px-4 rtds-rounded-md rtds-border rtds-inline-flex rtds-gap-1 rtds-items-center rtds-justify-center rtds-font-bold rtds-text-lg rtds-tracking-wide rtds-transition-all rtds-duration-200 rtds-ease-out rtds-leading-none;
        }
    
        .rtds-btn--primary {
            @apply rtds-button-primary rtds-text-white hover:rtds-text-white hover:rtds-button-primary-hover focus:rtds-button-primary-hover active:rtds-button-primary-active;
        }
    
        .rtds-btn--secondary {
            @apply rtds-button-secondary rtds-text-white hover:rtds-button-secondary-hover focus:rtds-button-secondary-hover active:rtds-button-secondary-active;
        }
    
        .rtds-btn--only-text {
            @apply rtds-bg-transparent rtds-button-only-text hover:rtds-button-only-text-hover hover:rtds-background-02 focus:rtds-button-only-text-hover focus:rtds-background-01 active:rtds-button-only-text-active;
        }
    
        .rtds-btn--outlined {
            @apply rtds-bg-transparent rtds-button-outline rtds-border rtds-border-current hover:rtds-button-primary hover:rtds-text-white focus:rtds-button-outline-hover focus:rtds-background-01;
        }
        
        .rtds-btn--inverted {
            @apply rtds-button-inverted rtds-button-inverted-text hover:rtds-button-inverted-hover hover:rtds-button-inverted-text-hover focus:rtds-button-inverted-focus focus:rtds-button-inverted-text-focus active:rtds-button-inverted-active active:rtds-button-inverted-text-active;
        }
    
        .rtds-btn--text-inverted {
            @apply rtds-button-text-inverted rtds-button-text-inverted-text hover:rtds-button-text-inverted-hover hover:rtds-button-text-inverted-text-hover focus:rtds-button-text-inverted-focus focus:rtds-button-text-inverted-text-focus active:rtds-button-text-inverted-active active:rtds-button-text-inverted-text-active;
        }
    
        /* UTILITY VARIANTS FOR SIZE MANAGING AND ICON MANAGING */
    
        .rtds-btn--icon {
            @apply rtds-rounded-full rtds-justify-center rtds-p-0 rtds-w-9 rtds-h-9;
        }
    
        .rtds-btn--icon-square {
            @apply rtds-rounded-md rtds-justify-center rtds-p-0 rtds-w-9 rtds-h-9;
        }
    
        /*.rtds-btn--icon-right,
        .rtds-btn--icon-left {
            @apply rtds-justify-between;
        }*/
    
        .rtds-btn--icon-right {
            @apply rtds-pr-3;
        }
    
        .rtds-btn--icon-left {
            @apply rtds-pl-3;
        }
    
        .rtds-btn--icon-right .rtds-icon,
        .rtds-btn--icon-left .rtds-icon {
            @apply rtds-transition-all rtds-duration-200 rtds-ease-out rtds-transform rtds-flex-none;
        }
    
        /* SIZE MANAGEMENT */
        .rtds-btn--s {
            @apply rtds-gap-0.5 rtds-text-sm;
        }
    
        .rtds-btn--l {
            @apply rtds-gap-2 rtds-px-6 rtds-py-3 rtds-text-xl;
        }
    
        /* SPACING MANAGEMENT FOR BUTTON WITH ICON */
        .rtds-btn--s:where(.rtds-btn--icon),
        .rtds-btn--s:where(.rtds-btn--icon-square) {
            @apply rtds-w-6 rtds-h-6;
        }
        .rtds-btn--s:where(.rtds-btn--icon-right),
        .rtds-btn--s:where(.rtds-btn--icon-left) {
            @apply rtds-gap-1.5;
        }
    
        .rtds-btn--s:where(.rtds-btn--icon-right) {
            @apply rtds-pr-3;
        }
    
        .rtds-btn--s:where(.rtds-btn--icon-left) {
            @apply rtds-pl-3;
        }
    
        .rtds-btn--l:where(.rtds-btn--icon),
        .rtds-btn--l:where(.rtds-btn--icon-square) {
            @apply rtds-w-12 rtds-h-12;
        }
        
        .rtds-btn--l:where(.rtds-btn--icon-right) {
            @apply rtds-pr-4;
        }
    
        .rtds-btn--l:where(.rtds-btn--icon-left) {
            @apply rtds-pl-4;
        }
    
        .rtds-btn--s:where(.rtds-btn--icon),
        .rtds-btn--l:where(.rtds-btn--icon),
        .rtds-btn--s:where(.rtds-btn--icon-square),
        .rtds-btn--l:where(.rtds-btn--icon-square) {
            @apply rtds-px-0;
        }
    
        /* DISABLED STATE */
        .rtds-btn[disabled] {
            background-color: var(--color-background-02);
            color: var(--color-button-disabled);
            border-color: var(--color-background-02);
            cursor: not-allowed;
        }
    
        .rtds-btn[aria-disabled="true"] {
            background-color: var(--color-background-02);
            color: var(--color-button-disabled);
            border-color: var(--color-background-02);
            cursor: not-allowed;
        }
    
    }
  • URL: /components/raw/button/button.css
  • Filesystem Path: components/02-atoms/button/button.css
  • Size: 4.2 KB

Button

Componente per pulsanti con supporto per diverse varianti, dimensioni e icone.


Comportamento

Il componente può essere renderizzato come:

  • <button> — elemento di default; garantisce ruolo button e attivabilità da tastiera nativa senza ARIA aggiuntivo
  • <a> — quando viene specificato href nel contesto Fractal/Nunjucks; usato per navigare a un URL con aspetto visivo di pulsante
  • <span> — quando viene specificato span: true nel contesto Fractal/Nunjucks; usato come contenuto non cliccabile all’interno di componenti complessi (es. card, list-item)

Varianti

Varianti di Stile

  • primary: Pulsante primario (default)
  • secondary: Pulsante secondario
  • outlined: Pulsante con bordo
  • outlined-bg-white: Pulsante con bordo e sfondo bianco fisso — da usare quando il pulsante outlined è posizionato su un contenitore non bianco (es. pattern già in uso nei servizi come card-smart--scadenza, o su sfondo scuro come in back-to-top-button)
  • inverted: Pulsante con stile invertito
  • text-inverted: Pulsante di testo con stile invertito
  • only-text: Pulsante di solo testo
  • icon: Pulsante circolare con solo icona
  • icon-right: Pulsante con icona a destra
  • icon-left: Pulsante con icona a sinistra

Varianti di Dimensione

  • size-s: Dimensione piccola
  • size-l: Dimensione grande

Varianti di Stato

  • spinner: Pulsante con indicatore di caricamento
  • disabled: Pulsante disabilitato con attributo HTML nativo disabled
  • disabled-aria: Pulsante disabilitato con aria-disabled="true" (interazione preservata)

Parametri di configurazione

Contesto di sviluppo Fractal/Nunjucks — Questa sezione riguarda esclusivamente i parametri del context .config.yml usati durante lo sviluppo nella libreria. Non descrive attributi HTML o proprietà da usare nei temi, CMS o ambienti di implementazione: per quello, fare riferimento alla sezione Esempi di utilizzo.

Parametri Base

  • label (stringa, obbligatorio): Testo del pulsante
  • classes (stringa, opzionale): Classi CSS aggiuntive
  • buttonType (stringa, opzionale): Tipo del pulsante (button, submit, reset). Default: “button”
  • href (stringa, opzionale): URL per renderizzare come link
  • span (booleano, opzionale): Renderizza come span invece che button/link
  • disabled (booleano, opzionale): Aggiunge l’attributo HTML disabled; lo stile viene applicato automaticamente via CSS
  • ariaDisabled (booleano, opzionale): Aggiunge aria-disabled="true"; stile visivo identico a disabled, ma l’interazione non è bloccata dal browser

Parametri per Icone

  • icon (stringa, opzionale): ID dell’icona da utilizzare
  • iconSize (stringa, opzionale): Classi CSS per la dimensione dell’icona
  • iconTransformType (stringa, opzionale): Tipo di animazione dell’icona al passaggio del mouse. Valori supportati: 'scale' (ingrandimento 125%), 'translate' (traslazione orizzontale)

Parametri di Accessibilità

  • labelHidden (booleano, opzionale): Nasconde il testo visibile ma mantiene l’accessibilità
  • srOnlyLabel (stringa, opzionale): Testo aggiuntivo visibile solo agli screen reader
  • ariaExpanded (booleano, opzionale): Stato di espansione per ARIA
  • ariaControls (stringa, opzionale): ID dell’elemento controllato per ARIA
  • ariaLabel (stringa, opzionale): Etichetta ARIA per il pulsante
  • id (stringa, opzionale): ID del pulsante
  • callback (stringa, opzionale): Funzione JavaScript da eseguire al click

Accessibilità

WCAG 2.1

Livello AA — criteri pertinenti:

  • 1.4.3 Contrasto minimo — testo su sfondo per tutte le varianti
  • 2.1.1 Tastiera — il <button> è attivabile da tastiera nativamente
  • 2.4.6 Intestazioni ed etichette — ogni pulsante ha un nome accessibile
  • 4.1.2 Nome, Ruolo, Valore — role implicito button; stato e controlli comunicati tramite ARIA

Struttura e semantica

  • <button> — ruolo implicito button; attivabile con Enter e Space; usare per azioni che non navigano a un URL
  • <a> — ruolo implicito link; attivabile con Enter; usare quando il componente deve navigare verso un URL
  • <span> — nessun ruolo interattivo; non focusabile; usare esclusivamente come contenuto non cliccabile all’interno di componenti complessi (es. card, list-item) dove l’interattività è gestita dal contenitore padre

Attributi ARIA

Attributo Valori Condizione
aria-label stringa descrittiva quando ariaLabel è presente
aria-expanded "false" (iniziale) quando ariaExpanded è attivo; aggiornato a "true" via JS all’apertura
aria-controls ID dell’elemento controllato quando ariaControls è presente
Tasto Azione
Tab / Shift+Tab Sposta il focus sul/dal pulsante
Enter / Space Attiva il pulsante

Gestione del focus

  • Focus visibile: lo stile :focus-visible è sempre visibile e non viene rimosso via CSS

Screen reader

  • Nei pulsanti solo icona, il testo è racchiuso in <span class="rtds-sr-only">; l’icona ha aria-hidden="true" — lo screen reader legge solo il testo nascosto come nome del pulsante, ignorando l’icona
  • Il <span class="rtds-sr-only"> opzionale dopo il testo visibile arricchisce il nome accessibile annunciato (es. “Elimina questo elemento dalla lista”)
  • aria-expanded comunica lo stato aperto/chiuso del pannello o menu controllato; aria-controls collega il pulsante all’elemento controllato — entrambi annunciati dallo screen reader insieme al nome del pulsante

Stili CSS

Classi Base

  • .rtds-btn: Stile base del pulsante
  • .rtds-btn--primary: Stile primario
  • .rtds-btn--secondary: Stile secondario
  • .rtds-btn--outlined: Stile con bordo
  • .rtds-btn--inverted: Stile invertito
  • .rtds-btn--text-inverted: Stile di testo invertito
  • .rtds-btn--only-text: Stile di solo testo

Classi Modificatori

  • .rtds-btn--icon: Stile per pulsante circolare con icona
  • .rtds-btn--icon-right: Stile per icona a destra
  • .rtds-btn--icon-left: Stile per icona a sinistra
  • .rtds-btn--s: Dimensione piccola
  • .rtds-btn--l: Dimensione grande

Stato Disabled

Lo stato disabilitato è gestito tramite selettori attributo, senza classi aggiuntive:

  • .rtds-btn[disabled]: stile disabilitato + blocco interazione (pointer-events: none)
  • .rtds-btn[aria-disabled="true"]: stile disabilitato visivo; il click e la navigazione da tastiera rimangono attivi

Stato Disabled — Indicazioni d’uso

disabled (attributo HTML nativo)

L’attributo disabled impedisce all’utente di effettuare azioni non valide o non disponibili in un dato momento: l’elemento viene rimosso dal tab order, l’attivazione da tastiera e mouse è bloccata, e viene annunciato dagli screen reader come “non disponibile”. Utile ad esempio per bloccare l’invio di un form finché i campi obbligatori non sono compilati, o per disattivare un’azione che richiede una condizione non ancora soddisfatta.

Non è sempre la soluzione migliore: un pulsante rimosso dal tab order è invisibile alla navigazione da tastiera, e senza un messaggio esplicativo l’utente non capisce perché l’azione non è disponibile né cosa fare per sbloccarla. Valutare sempre se non sia preferibile un’alternativa (vedi avvertenza generale).

<button class="rtds-btn rtds-btn--primary" disabled>
  Invia
</button>

aria-disabled="true"

Comunica lo stato disabilitato agli screen reader, ma non blocca la navigazione da tastiera né il click — lo sviluppatore deve gestire il blocco dell’interazione via JavaScript. Usare quando il pulsante deve restare raggiungibile, ad esempio per mostrare un tooltip che spiega perché l’azione non è disponibile.

<button class="rtds-btn rtds-btn--primary" aria-disabled="true">
  Invia
</button>

Nota implementativa: Con aria-disabled="true", il click handler rimane attivo. Aggiungere un handler che ignora l’evento o mostra un messaggio contestuale.

Avvertenza generale

Evitare i pulsanti disabilitati dove possibile. Un pulsante disabilitato senza spiegazione crea frustrazione: l’utente non sa perché non può agire né cosa fare per sbloccare l’azione. Prima di usare lo stato disabled, discutere con il team di sviluppo le alternative:

  • Mostrare un messaggio contestuale che spiega la condizione bloccante
  • Rendere il pulsante attivo ma mostrare un feedback al click (es. “Compila prima tutti i campi obbligatori”)
  • Usare uno stato di loading invece di disabled durante operazioni asincrone

Se il disabled è inevitabile, fornire sempre un testo o un tooltip adiacente che spieghi il motivo.


Esempi di utilizzo

<!-- Pulsante primario -->
<button class="rtds-btn rtds-btn--primary">
  Salva
</button>

<!-- Pulsante secondario -->
<button class="rtds-btn rtds-btn--secondary">
  Annulla
</button>

<!-- Pulsante con icona a destra -->
<button class="rtds-btn rtds-btn--primary rtds-btn--icon-right">
  Continua
  <svg class="rtds-icon">...</svg>
</button>

<!-- Pulsante circolare con icona (accessibile) -->
<button class="rtds-btn rtds-btn--primary rtds-btn--icon">
  <span class="rtds-sr-only">Chiudi</span>
  <svg class="rtds-icon">...</svg>
</button>

<!-- Pulsante per menu dropdown -->
<button class="rtds-btn rtds-btn--primary"
        aria-expanded="false"
        aria-controls="dropdown-menu">
  Menu
  <svg class="rtds-icon">...</svg>
</button>