# Buttons and links

## A button that navigates

Use an `a` element styled as a button when the action opens a page, email address or telephone number. A `button type="submit"` submits a form and is owned by the form component. Replacing it with a link removes submission and validation behavior.

The complete `blocks/button.html` below exposes label, destination, visibility, colors, border, radius, horizontal/vertical padding and text size. Every control has a real default and affects the rendered style.

## 1. Label and destination

```html
<a href="[[ safe_url(block.settings.url) ]]">
  <span data-text-field="[[ block.settings_path ]].label" gs-text="block.settings.label"></span>
</a>
```

Declare a `text` setting named `label` and a `url` setting named `url`. `safe_url` validates the destination. Binding the span enables text editing. This example edits its destination and appearance through schema controls; an ordinary anchor does not automatically receive every specialized legacy button editor feature.

| Destination | URL value |
| --- | --- |
| Site page | `/contact` |
| Page section | `/#contact` — the target must have that ID |
| External website | `https://example.com` |
| Email | `mailto:hello@example.com` |
| Telephone | `tel:+31201234567` |
| WhatsApp | `https://wa.me/31201234567` |

Use an international WhatsApp number without a plus sign or spaces. URL-encode any message parameters. Do not use executable URL schemes or inline handlers. The schema `url` control accepts a complete address; the platform's composite link-type selector is a separate editing mechanism.

## 2. Appearance and dimensions

Use precise labels: border width, corner radius, vertical padding, horizontal padding and text size. Use a range with numeric input, unit and limits. Declare colors as `color` to use the shared picker.

```json
{"type":"range","id":"padding_x","label":"Horizontal padding","min":8,"max":64,"step":1,"default":20,"unit":"px"}
```

`padding:12px 20px` means 12 px at top/bottom and 20 px at the sides. Border width zero hides the border; radius zero produces square corners. Do not replace zero with a default using a truthiness check.

## 3. States and accessibility

Add inline-flex layout, centered content, readable contrast, a 44 px minimum height, hover and visible keyboard focus styles. Do not remove the focus outline without providing a replacement. Labels should describe the action, such as “Contact us”.

Links open in the same tab by default. If a new tab is intentional, use `target="_blank"` with `rel="noopener noreferrer"` and communicate that behavior. Ordinary navigation does not require click handlers.

## 4. Icons and visibility

The example intentionally has no icon. Add one only when it will actually render; do not represent an icon-free button with a default star. See [cards and media](/en/media/) for icon integration.

The `show_link` setting hides the whole element with a Loom condition. Deleting the block instance removes it. An empty URL is not a meaningful destination: configure one or disable the link.

## Form submit buttons

`ContactFormPlugin` disables submission while busy and shows status below the button. The Paylio footer binds its submit text to `content.badge`. Do not use a mail link as a substitute for submission. Follow the [forms guide](/en/forms/).

## Final checks

Verify persisted label and URL, each destination type, zero border/radius, long mobile text, keyboard access and focus. Test navigation on the public preview because edit mode may intercept clicks.

## Complete block file


See [GS Loom UI components](/en/components/) for primary, secondary, outline, ghost, link and danger variants, sizes, theme tokens and editable settings.

### blocks/button.html

```html
<template gs-if="block.settings.show_link">
<a class="launch-cta" href="[[ safe_url(block.settings.url) ]]" style="background:[[ block.settings.background ]];color:[[ block.settings.color ]];border:[[ block.settings.border_width ]]px solid [[ block.settings.border_color ]];border-radius:[[ block.settings.radius ]]px;padding:[[ block.settings.padding_y ]]px [[ block.settings.padding_x ]]px;font-size:[[ block.settings.font_size ]]px">
  <span data-text-field="[[ block.settings_path ]].label" gs-text="block.settings.label"></span>
</a>
</template>
```
