Skip to content

Button

Button
Thymeleaf source
<sl:button>Save</sl:button>
Rendered HTML
<button class="button" type="button">Save</button>

<sl:button> renders type="button", not HTML’s default submit. A button inside a form therefore does nothing unless you say so: write type="submit" on the one that should submit.

In a formOnly the button that says type="submit" submits the form.
Thymeleaf source
<form method="post" th:action="@{/orders}">
<sl:button type="submit">Save</sl:button>
<sl:button variant="outline">Preview</sl:button>
</form>
Rendered HTML
<form method="post" action="/orders">
<button class="button" type="submit">Save</button>
<button class="button" type="button" data-variant="outline">Preview</button>
</form>
Variants
Thymeleaf source
<sl:button>Default</sl:button>
<sl:button variant="outline">Outline</sl:button>
<sl:button variant="ghost">Ghost</sl:button>
<sl:button variant="destructive">Destructive</sl:button>
<sl:button variant="secondary">Secondary</sl:button>
<sl:button variant="link">Link</sl:button>
Rendered HTML
<button class="button" type="button">Default</button>
<button class="button" type="button" data-variant="outline">Outline</button>
<button class="button" type="button" data-variant="ghost">Ghost</button>
<button class="button" type="button" data-variant="destructive">Destructive</button>
<button class="button" type="button" data-variant="secondary">Secondary</button>
<button class="button" type="button" data-variant="link">Link</button>
Sizes
Thymeleaf source
<sl:button size="xs">Extra small</sl:button>
<sl:button size="sm">Small</sl:button>
<sl:button>Default</sl:button>
<sl:button size="lg">Large</sl:button>
Rendered HTML
<button class="button" type="button" data-size="xs">Extra small</button>
<button class="button" type="button" data-size="sm">Small</button>
<button class="button" type="button">Default</button>
<button class="button" type="button" data-size="lg">Large</button>

The icon sizes are square, for a button holding only an icon. Such a button has no text for a screen reader, so it must have an aria-label (or aria-labelledby); rendering fails without one.

Icon-only sizesAn icon-only button needs an aria-label; rendering fails without one.
Thymeleaf source
<sl:button size="icon-xs" variant="outline" aria-label="Add"><sl:icon name="plus"/></sl:button>
<sl:button size="icon-sm" variant="outline" aria-label="Add"><sl:icon name="plus"/></sl:button>
<sl:button size="icon" variant="outline" aria-label="Add"><sl:icon name="plus"/></sl:button>
<sl:button size="icon-lg" variant="outline" aria-label="Add"><sl:icon name="plus"/></sl:button>
Rendered HTML
<button class="button" type="button" data-variant="outline" data-size="icon-xs" aria-label="Add">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
</button>
<button class="button" type="button" data-variant="outline" data-size="icon-sm" aria-label="Add">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
</button>
<button class="button" type="button" data-variant="outline" data-size="icon" aria-label="Add">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
</button>
<button class="button" type="button" data-variant="outline" data-size="icon-lg" aria-label="Add">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
</button>

Put an <sl:icon> in the icon-start or icon-end slot. It takes the button’s colour and a 16 pixel size.

With an icon
Thymeleaf source
<sl:button variant="destructive">
<sl:slot name="icon-start"><sl:icon name="trash"/></sl:slot>
Delete order
</sl:button>
<sl:button variant="outline">
Next
<sl:slot name="icon-end"><sl:icon name="arrow-right"/></sl:slot>
</sl:button>
Rendered HTML
<button class="button" type="button" data-variant="destructive">
<span class="button-icon" data-icon="inline-start">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M10 11v6"></path>
<path d="M14 11v6"></path>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path>
<path d="M3 6h18"></path>
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
</span>
Delete order
</button>
<button class="button" type="button" data-variant="outline">
Next
<span class="button-icon" data-icon="inline-end">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
<path d="m12 5 7 7-7 7"></path>
</svg>
</span>
</button>

as="a" renders an anchor that looks the same, for navigation.

As a link
Thymeleaf source
<sl:button as="a" href="/orders" variant="outline">View orders</sl:button>
<sl:button as="a" href="/orders" variant="link">View orders</sl:button>
Rendered HTML
<a class="button" data-variant="outline" href="/orders">View orders</a>
<a class="button" data-variant="link" href="/orders">View orders</a>

A disabled button gets the native disabled attribute. A disabled link loses its href, leaves the tab order and is marked aria-disabled, so it cannot be followed. loading shows a spinner in place of the start icon, announces the busy state with aria-busy and a visually hidden “Loading” label, and disables the button.

Disabled
Thymeleaf source
<sl:button disabled>Save</sl:button>
<sl:button as="a" href="/orders" variant="outline" disabled>View orders</sl:button>
Rendered HTML
<button class="button" type="button" disabled>Save</button>
<a class="button" data-variant="outline" aria-disabled="true" role="link" tabindex="-1">View orders</a>
Loading
Thymeleaf source
<sl:button loading>Saving</sl:button>
<sl:button variant="outline" loading>
<sl:slot name="icon-start"><sl:icon name="save"/></sl:slot>
Save
</sl:button>
<sl:button size="icon" variant="outline" aria-label="Refresh" loading><sl:icon name="refresh-cw"/></sl:button>
Rendered HTML
<button class="button" type="button" aria-busy="true" disabled>
<span class="button-icon" data-icon="inline-start">
<svg xmlns="http://www.w3.org/2000/svg" class="icon button-spinner" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
</svg>
</span>
<span class="sl-sr-only">Loading</span>
Saving
</button>
<button class="button" type="button" data-variant="outline" aria-busy="true" disabled>
<span class="button-icon" data-icon="inline-start">
<svg xmlns="http://www.w3.org/2000/svg" class="icon button-spinner" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
</svg>
</span>
<span class="sl-sr-only">Loading</span>
Save
</button>
<button class="button" type="button" data-variant="outline" data-size="icon" aria-busy="true" aria-label="Refresh" disabled>
<span class="button-icon" data-icon="inline-start">
<svg xmlns="http://www.w3.org/2000/svg" class="icon button-spinner" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
</svg>
</span>
<span class="sl-sr-only">Loading</span>
</button>

The “Loading” label comes from the message sl.button.loading; define it in your own messages.properties to translate or reword it.

Every prop also has a th: form that takes a Thymeleaf expression.

Props from an expression
Thymeleaf source
<sl:button th:variant="${order.cancellable ? 'destructive' : 'secondary'}"
th:text="#{orders.cancel}">Cancel order</sl:button>
Rendered HTML
<button class="button" type="button" data-variant="destructive">Cancel order</button>

Attributes that are not props, such as hx-*, x-*, aria-* and data-*, land on the rendered element unchanged, so the button works with htmx and Alpine. class is appended to the component’s own class.

Pass-through attributesAnything that is not a prop lands on the rendered element; class is appended.
Thymeleaf source
<sl:button variant="outline" class="app-wide"
hx-post="/orders/42/archive" hx-target="#orders"
data-testid="archive">Archive</sl:button>
Rendered HTML
<button class="button app-wide" type="button" data-variant="outline" hx-post="/orders/42/archive" hx-target="#orders" data-testid="archive">Archive</button>
AttributeValuesDefaultDescription
variantdefault outline ghost destructive secondary linkdefaultVisual style of the button.
sizexs sm default lg icon icon-xs icon-sm icon-lgdefaultSize of the button. The icon sizes are square, for a button that holds only an icon, and need an aria-label.
asbutton abuttonElement to render: a button, or an anchor (a) for navigation.
typebutton submit resetbuttonSubmit behaviour. Defaults to button, unlike HTML, so a button in a form never submits by accident.
disabledboolean—Disable the button. A disabled anchor loses its href and leaves the tab order.
loadingboolean—Show a spinner, announce the busy state and disable the button.

Every prop can also be set from an expression: th:variant="${...}". Any other attribute passes through to the rendered element, and class is appended. An aria-label or aria-labelledby is required when size is icon, icon-xs, icon-sm or icon-lg; rendering fails without one.