<a href="#" class="rtds-btn
rtds-btn--only-text ">
only text
<svg class="rtds-icon rtds-fill-current rtds-w-4 rtds-h-4 md:rtds-w-5 md:rtds-h-5 lg:rtds-w-6 lg:rtds-h-6 rtds-transition-all rtds-duration-200 rtds-ease-out rtds-transform group-hover:rtds-translate-x-1" aria-hidden="true" focusable="false" role="img">
<use href="../../icons.svg#mini--chevron-right" />
</svg>
</a>
{% extends '@button' %}
{%- block classes -%}
rtds-btn--only-text {{ classes }}
{%- endblock classes -%}
{%- block after -%}
{% render '@icon--small', { id: icon, classes: 'rtds-transition-all rtds-duration-200 rtds-ease-out rtds-transform group-hover:rtds-translate-x-1', size: 'rtds-w-4 rtds-h-4 md:rtds-w-5 md:rtds-h-5 lg:rtds-w-6 lg:rtds-h-6' }, true %}
{%- endblock after -%}
{
"label": "only text",
"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",
"href": "#",
"icon": "mini--chevron-right"
}
/**
* 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;
}
/**
* 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;
}
}
Componente per pulsanti con supporto per diverse varianti, dimensioni e icone.
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)primary: Pulsante primario (default)secondary: Pulsante secondariooutlined: Pulsante con bordooutlined-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 invertitotext-inverted: Pulsante di testo con stile invertitoonly-text: Pulsante di solo testoicon: Pulsante circolare con solo iconaicon-right: Pulsante con icona a destraicon-left: Pulsante con icona a sinistrasize-s: Dimensione piccolasize-l: Dimensione grandespinner: Pulsante con indicatore di caricamentodisabled: Pulsante disabilitato con attributo HTML nativo disableddisabled-aria: Pulsante disabilitato con aria-disabled="true" (interazione preservata)Contesto di sviluppo Fractal/Nunjucks — Questa sezione riguarda esclusivamente i parametri del context
.config.ymlusati 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.
label (stringa, obbligatorio): Testo del pulsanteclasses (stringa, opzionale): Classi CSS aggiuntivebuttonType (stringa, opzionale): Tipo del pulsante (button, submit, reset). Default: “button”href (stringa, opzionale): URL per renderizzare come linkspan (booleano, opzionale): Renderizza come span invece che button/linkdisabled (booleano, opzionale): Aggiunge l’attributo HTML disabled; lo stile viene applicato automaticamente via CSSariaDisabled (booleano, opzionale): Aggiunge aria-disabled="true"; stile visivo identico a disabled, ma l’interazione non è bloccata dal browsericon (stringa, opzionale): ID dell’icona da utilizzareiconSize (stringa, opzionale): Classi CSS per la dimensione dell’iconaiconTransformType (stringa, opzionale): Tipo di animazione dell’icona al passaggio del mouse. Valori supportati: 'scale' (ingrandimento 125%), 'translate' (traslazione orizzontale)labelHidden (booleano, opzionale): Nasconde il testo visibile ma mantiene l’accessibilitàsrOnlyLabel (stringa, opzionale): Testo aggiuntivo visibile solo agli screen readerariaExpanded (booleano, opzionale): Stato di espansione per ARIAariaControls (stringa, opzionale): ID dell’elemento controllato per ARIAariaLabel (stringa, opzionale): Etichetta ARIA per il pulsanteid (stringa, opzionale): ID del pulsantecallback (stringa, opzionale): Funzione JavaScript da eseguire al clickLivello AA — criteri pertinenti:
<button> è attivabile da tastiera nativamenterole implicito button; stato e controlli comunicati tramite ARIA<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| 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 |
:focus-visible è sempre visibile e non viene rimosso via CSS<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<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.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.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 grandeLo 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 attividisabled (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.
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.
<!-- 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>