The button come in two sizes: 50 and 100. Use button 100, the larger size, as the default. Only use button 50 in areas with limited space because it provides an suboptimal hitbox size.
The button is available in the following variants:
Primary: Use for the most important action on a page. Use only once per view.
Secondary: Use for less important actions.
Secondary-over-color: Use for less important actions on frame, stacked or colored backgrounds.
Tertiary: Has the least visual attention and functions like links but are styled as buttons.
Success: Use to indicate a positive outcome of an action. The success button is displayed as a variant because it has its own states.
Custom-color-soft: Use semantic design tokens with the prefix "$oc-semantic-color-background-soft-" for the background and use semantic design tokens with sufficient color contrast for the text. Please check with the design system team before doing so.
Custom-color-strong: Use semantic design tokens with the prefix "$oc-semantic-color-background-strong-" for the background and use semantic design tokens with sufficient color contrast for the text. Please check with the design system team before doing so.
Icons are optional and can be placed on either the left or right side of the button. The decorative icon on the left reduces cognitive load by symbolizing the button’s action. The icon on the right serve as an affordance for interaction, such as an arrow pointing forward for navigation.
Loading button
Buttons can be put into a loading state. This replaces the icons and the label with a centered spinner. This feature should be used for actions expected to complete asynchronously. During loading, the button has no interactive states and is not interactive.
You can start a positive action with a primary, secondary or tertiary button. Once the positive action is completed, the button changes to the success variant. Make sure the size (100, 50) and fitting of the success button match the preceding button.
Only use the success variant in combination with a check icon, as there should be redundant cues for the state.
According to your needs, you can choose whether the positive action can be undone. If you do not want the positive action to be reversible, use the disabled state of the Success Button.
Reversible
Folge ich
Klick oder Tipp auf den Success-Button macht die Aktion rückgängig
Folgen
Not reversible
Bewertung gesendet
Das positive Ergebnis lässt sich nicht rückgängig machen
The horizontal fitting of the button can behave in two different ways: fill-parent and fit-content. The default setting is fit-content. In contrast, the fill-parent button extends across the entire parent element, with the label centered. Generally, if the label exceeds the button's width, it will be truncated, which is best avoided.
The vertical fitting of the button is fit-content.
When placing multiple buttons horizontal or vertical to each other, make sure there is a gap of 8px in-between. When placing buttons horizontal to each other, the primary button should be on the right. When placing buttons vertical to each other, the primary button should be on top. Always make sure that there are at least 8 pixels in between buttons.
DoShow the full text on buttons. If necessary, they should be stacked when they cannot be placed side by side.
Rücksendung anmeldenBestellung ansehen
Don'tTruncate the button text.
Sneaker „Mojo“ aus dem neuen Fashion-Drop, jetzt mit Extra-Rabatt.
-12 %-20 % Extra
MerkenIn den Warenkorb
DoUse few buttons for only the relevant actions.
Sneaker „Mojo“ aus dem neuen Fashion-Drop, jetzt mit Extra-Rabatt.
-12 %-20 % Extra
TeilenVergleichen
MerkenIn den Warenkorb
Größe wählenBewertungen
Don'tDon't clutter your UI with too many buttons.
SpeichernAbbrechen
DoUse the colors assigned by the design system.
SpeichernAbbrechen
Don'tDon't use custom colors for buttons. The colors of different button variations have been designed to be consistent and accessible.
Rücksendung
Du hast 30 Tage Zeit, deine Artikel kostenlos zurückzuschicken. Melde die Rücksendung in deinem Kundenkonto an, druck das Etikett aus und gib das Paket bei Hermes ab. Sobald es bei uns ist, bekommst du dein Geld zurück.
Rücksendung anmeldenBestellungen ansehen
DoPlace buttons inside a block.
Rücksendung
Du hast 30 Tage Zeit, deine Artikel kostenlos zurückzuschicken. Melde die Rücksendung in deinem Kundenkonto an, druck das Etikett aus und gib das Paket bei Hermes ab. Sobald es bei uns ist, bekommst du dein Geld zurück. Bei Speditionsware holen wir die Ware bei dir ab: Vereinbare dafür einfach einen Termin. Artikel, die du im Laden gekauft hast, kannst du auch dort zurückgeben. Bitte leg den Lieferschein bei, damit wir die Rücksendung schnell zuordnen können.
Rücksendung anmelden
CautionButtons can be used as sticky elements with a shadow300.
Folgen
DoOnly use the secondary-over-color variant on frame, stacked or colored background.
Folgen
Don'tUse the secondary-over-color button on a white or canvas background.
FolgenFolge ich
DoChange the label to a positive outcome when using the success button.
FolgenFolgen
Don'tUse the same label for both the positive action and the positive outcome.
FolgenFolge ich
CautionMake sure to use the success button sparingly and only to display specific positive outcomes, such as "Marke folgen".
In den Warenkorb
DoUse decorative icons for the left icon and affordance icons for the right icon.
In den Warenkorb
Don'tUse decorative icons for the right icon and affordance icons for the left icon.
Content guidelines
Do1. Use simple, clear language.
Keep the label concise, under 4 words and fewer than 20 characters, including spaces.
Capitalize the first letter of the first word and proper nouns in
the button label text.
Use a verb that describes the action, such as "Zum Warenkorb hinzufügen".
Contextualize the action the user is taking.
Maintain consistent wording for the same button across different views.
Avoid punctuation marks such as periods or exclamation points.
Don't1. Use ambiguous wording or overly creative content, as they may not be accessible.
Include polite expressions like "please" and "thank you."
Use non-specific phrases like "learn more" or "click here."
Use emojis or exclamation points, as they aren’t appropriate for the functional nature of buttons.
Write labels as nouns or adjectives, which can be unclear and disorienting.
Accessibility
When writing accessible button labels, it's crucial to ensure that it provides sufficient context about the button's behavior. If a label like "Weiter" or "Mehr anzeigen" is used, it can be confusing for screen reader users when read out of context. In such cases, it's necessary to provide an alternative text with deeper context, such as "Weiter zur Kasse" or "Mehr ähnliche Produkte anzeigen," using the aria-label attribute. This attribute helps to describe the button's function fully, considering the rest of the website content is not visible to the screen reader.
For information on accessibility, refer to the technical documentation.
Status
Related components
Icon Button: Rounded Buttons consist of a single icon on a white circle
Link: A link allows users to navigate through applications and websites.
The button component is used to trigger specific actions in your application.
It supports various styles, sizes, and can incorporate icons.
It can be configured to fit content, be disabled, and set with an ARIA label for accessibility.
The button component is available in the main styling variants primary, secondary, and tertiary.
This component is configurable, allowing you to tailor its features and appearance to your specific needs.
To explore all the available options and adjust the component, use the component configurator and see the changes affect the component in real-time.
Usage guidelines
Before integrating the button component into your project, make sure you have correctly installed the OTTO components package.
Look through the variations page for examples of possible component variations. Here, you can discover both common and specific variations that address different use cases.
If the button component or a normal HTML button with the type submit is used in a form, pressing Enter on any of the following form elements triggers the submit action immediately (implicit submit): oc-checkbox, oc-radio-button, oc-switch-v2, oc-selection-tile, and oc-text-field.
Use oc-aria-label
To make the button recognizable for screen readers, use the oc-aria-label attribute to provide clear and descriptive context information.
See the general accessibility documentation for guidance on using oc-aria-label, including how it works with link and masked link behavior.
Multiple buttons
When multiple buttons with the same visible label are present, ensure each button is uniquely labeled so users understand their purpose.
The icon inside the button component has the state aria-hidden="true"
, which hides the icon from assistive technologies and prevents it from being read out loud by screen readers.
Configure the button component with the controls below and see the changes live in the preview canvas.
Click the Show code button within the preview canvas to see the source code for the current component configuration.
Interactive configurator (Storybook controls); every option is listed in the API section of this file.
API v1
Button v1 API
API: <oc-button-v1> (ButtonV1)
The Button component is used to trigger specific actions in your application.
It supports various styles, sizes, and can incorporate icons.
It can be configured to fit content, be disabled, and set with an ARIA label for accessibility.
Note:"secondary-on-color" is deprecated, use "secondary-over-color" instead.
type
"submit" | "reset" | "button"
undefined
no
Sets the type of the button. Can be one of: "button", "reset", "submit".
size
"50" | "100"
"100"
no
Sets the size of the button.
icon-type-left
icon name (428 values; see Icon list in `storybook/components/icon/README.md`)
undefined
no
Sets the icon for the left side of the button.
icon-type-right
icon name (428 values; see Icon list in `storybook/components/icon/README.md`)
undefined
no
Sets the icon for the right side of the button.
disabled
boolean
false
no
Indicates whether the button is disabled.
loading
"" | "keep-native-events" | "true"
false
no
Indicates whether the button is in a loading state. When enabled, displays a loading animation.
For buttons of type "submit", the loading state also stops the propagation of click events after the first click to prevent multiple form submissions. Set to "keep-native-events" to enable the loading state but keep native events for this case.
Note: The loading animation overwrites the icon on the right side set via icon-type-right.
success
boolean
false
no
Indicates whether the button is in a success state. This is a visual state that indicates a successful action. It does not change the functionality of the button.
fit-content
boolean
false
no
Indicates whether the button width should fit its content. This attribute only applies to size="100".
fill-parent
boolean
false
no
Indicates whether the button width should fill to its parent. This attribute only applies to size="50".
form
string
undefined
no
Sets the form id the button belongs to. Only available as an attribute.
formaction
string
undefined
no
Sets the action for form submission. Only available as an attribute. Only for type="submit".
formmethod
string
undefined
no
Sets the method for form submission. Only available as an attribute. Only for type="submit".
href
string
undefined
no
Deprecated: Use an a tag in the default slot instead.
Fired when the component is mounted to the DOM. The event is fired when the onMount hook of the component is called by the runtime.
oc-unmount
{ component: string; }
Fired when the component is unmounted from the DOM. The event is fired when the function returned by the onMount hook of the component is called by the runtime.
CSS custom properties
Custom property
Default
Description
--background-color
undefined
Sets a custom background color through a CSS variable.
Note: The preferred way of using colors is via design tokens instead of hex values.
Note: This CSS variable only applies when using variant="custom-color-strong" or variant="custom-color-soft".
--text-color
undefined
Sets a custom text color through a CSS variable.
Note: The preferred way of using colors is via design tokens instead of hex values.
Note: This CSS variable only applies when using variant="custom-color-strong" or variant="custom-color-soft".
Variations (v1)
Variations
Listed below are the most common variations of the button component as well as specific component variations for different use cases.
You can explore all available options using the component configurator, adjust the component to your needs, and see the changes live in a preview canvas.
The primary variant, with size=100, and the setting for showing a loading spinner.
Make sure to place the button as well as the placeholder / actual content in a common
parent container. This way - together witha couple of aria roles -, a screen reader is able to
identify the container as a status / live region that is being updated.
The button component as a link. Search engines will detect the href.
Args: defaultSlot=<!--The <a> tag should be placed inside the light DOM to improve SEO.--><a href='#'>Label</a>
<oc-button-v1 variant="primary" size="100">
<!--The <a> tag should be placed inside the light DOM to improve SEO.--><a href='#'>Label</a>
</oc-button-v1>
The button component as a link with switch behavior. Search engines will detect the href, while users receive the Base64-encoded version.
Args: base64-href=Iz92YXJpYW50PWZvbw==, defaultSlot=<!--Search engines will detect the href, while users receive the Base64-encoded version.--><a href='#'>Label</a>
<oc-button-v1 variant="primary" size="100" base64-href="Iz92YXJpYW50PWZvbw==">
<!--Search engines will detect the href, while users receive the Base64-encoded version.--><a href='#'>Label</a>
</oc-button-v1>
Comprehensive demo showcasing multiple buttons with custom background and text colors using design tokens.
This demo demonstrates the flexibility of the custom-color-strong and custom-color-soft variants.
Story source (TypeScript, verbatim from Storybook)
{
args: {
variant: "primary",
size: "100",
type: "button"
},
parameters: {
// Disables Chromatic's snapshotting, as the spinner is animated und thus leads to diffs
chromatic: {
disableSnapshot: true
}
},
render(args) {
return html` <oc-button-v1 ${spread(args)}>Button</oc-button-v1>`;
},
play: async ({
canvasElement
}) => {
// given
const ocButton = canvasElement.getElementsByTagName("oc-button-v1").item(0)!;
const button = getByShadowRole(canvasElement, "button");
button.addEventListener("click", () => {
ocButton.setAttribute("loading", "");
});
// there should be no spinner before clicking the button
const noSpinner = Array.from(button.childNodes).find(node => node.nodeName === "OC-SPINNER-V1");
await expect(noSpinner, "no spinner should be rendered before clicking the button").toBeFalsy();
// when
await click(button);
// then
// wait for some time because of the default debounce (?)
await waitFor("spinner to arrive", async () => {
const spinner = Array.from(button.childNodes).find(node => node.nodeName === "OC-SPINNER-V1");
await expect(spinner, "spinner should be rendered after clicking the button").toBeTruthy();
});
}
}
Story source (TypeScript, verbatim from Storybook)
{
render() {
return html` <oc-button-v1 type="submit">Button</oc-button-v1>`;
},
play: async ({
canvasElement
}) => {
const ocButton = canvasElement.getElementsByTagName("oc-button-v1").item(0)!;
const button = getByShadowRole(canvasElement, "button");
let clickCount = 0;
let documentClickCount = 0;
document.addEventListener("click", () => {
documentClickCount++;
});
ocButton.addEventListener("click", () => {
clickCount++;
});
// when
await click(button);
// then
await expect(clickCount, "direct click count should be 1 after first click").toBe(1);
await expect(documentClickCount, "document click count should be 1 after first click").toBe(1);
// when loading is set to true
ocButton.setAttribute("loading", "");
// give it time to settle
await waitFor("spinner to arrive", async () => {
const spinner = Array.from(button.childNodes).find(node => node.nodeName === "OC-SPINNER-V1");
await expect(spinner, "spinner should be rendered after clicking the button").toBeTruthy();
});
await click(button);
// then the click event should not be triggered again
await expect(clickCount, "direct click count should increase when loading is true").toBe(2);
// TODO slop: the document click count should not increase because the click event should not propagate, but currently it does for the first click after setting loading to true. This is because the bouncer logic is only applied on subsequent clicks, but not on the first click after setting loading to true. This is a known issue and will be fixed in the next iteration.
await expect(documentClickCount, "document click count should not increase when loading is true, but does for the first click after setting loading to true").toBe(2);
await click(button);
// then the click event should not be triggered again
await expect(clickCount, "direct click count should increase when loading is true").toBe(3);
await expect(documentClickCount, "document click count should not increase when loading is true, and now works for the second click after setting loading to true").toBe(2);
// and the spinner should be rendered inside the button
const spinner = Array.from(button.childNodes).find(node => node.nodeName === "OC-SPINNER-V1");
await expect(spinner, "spinner should be rendered when loading is true").toBeTruthy();
}
}
Story source (TypeScript, verbatim from Storybook)
{
render() {
return html` <oc-button-v1 base64-href="L2Zvbw==">Link</oc-button-v1>`;
},
async play({
canvasElement
}) {
const ocButton = canvasElement.getElementsByTagName("oc-button-v1").item(0)!;
ocButton.focus();
let linkElement: HTMLAnchorElement;
await waitFor("render link element inside shadow root", async () => {
// instead of the button element there should now be an anchor element
const buttonElement = ocButton.shadowRoot!.querySelector("button");
linkElement = ocButton.shadowRoot!.querySelector("a")!;
await expect(buttonElement, "no button should be rendered").toBeNull();
await expect(linkElement.tagName, "anchor should be rendered").toBe("A");
});
// anchor element inside the shadow root should have the correct href attribute
await expect(linkElement!.getAttribute("href")).toBe(`/foo`);
// anchor element inside the shadow root should have the same fully href as the host
await expect(ocButton.href).toBe(`${window.location.origin}/foo`);
await expect(linkElement!.href).toBe(`${window.location.origin}/foo`);
}
}