OTTODesign System

Code

Toggletip

Storybook group: Components · Sidebar path: Components/Toggletip · Extracted 28.09.2026

Version Tag Status API
v1 <oc-toggletip-v1> Stable, allowed for generation ToggletipV1

Overview (v1)

Source: ./src/components/toggletip/v1/Overview.mdx

Toggletip

The toggletip component provides a small non-modal dialog box that displays additional contextual information or hints when clicking an interactive element. It supports custom content and offers configuration options for positioning either above or below the triggering element, as well as controlling its initial visibility state.

Default variation

Story Default:

<oc-toggletip-v1 oc-aria-label="Einfacher Toggletip">
  <span slot='toggletip-content'>Hier ist der Toggletip!</span>
  <oc-button-v1 oc-aria-label="Für mehr Informationen klicken">Dieser Text hat einen Toggletip!</oc-button-v1>
</oc-toggletip-v1>

Configuration

The toggletip 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 how the changes affect the component in real time.

Usage guidelines

Before integrating the toggletip 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.

The following guidelines apply to the toggletip component:

  • You must make sure that the default slot contains an interactive element. This should preferably be a button, or a component with the as-button attribute.
  • Ensure that the interactive element in the default slot correctly indicates to accessibility tools that it opens a toggletip. If the content itself is not sufficient for this, use the oc-aria-label attribute on it to provide additional information.
  • The interactive element must not have its own click handler or on-click behavior. This might lead to unexpected results.
  • The toggletip-content slot should not contain interactive or block elements.

Info

See the Toggletip UX documentation for detailed user experience guidelines.

Accessibility

The toggletip component comes with a set of built-in accessibility features to ensure a seamless experience for all users.

It follows the interaction model of a non-modal dialog, as defined by the dialog ARIA role.

Labeling

The aria-labelledby attribute on the toggletip references its heading if present, and its content otherwise.

Keyboard navigation

The toggletip supports standard keyboard navigation for interactive elements:

Shortcut Description
Tab Moves focus to the toggletip trigger
Enter/Space Opens the toggletip
Esc Closes the toggletip when open
Keyboard focus management

When the toggletip is opened via the keyboard, the focus moves to the close button. When the toggletip is subsequently closed again, the focus returns to the interactive element that opened it.

Focus handling

The toggletip does not trap the focus, meaning that users can navigate away from it using the Tab key.

Configuration (v1)

Source: ./src/components/toggletip/v1/Configuration.mdx

Toggletip configuration

Configure the toggletip 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.

Story Default:

<oc-toggletip-v1 oc-aria-label="Einfacher Toggletip">
  <span slot='toggletip-content'>Hier ist der Toggletip!</span>
  <oc-button-v1 oc-aria-label="Für mehr Informationen klicken">Dieser Text hat einen Toggletip!</oc-button-v1>
</oc-toggletip-v1>

Interactive configurator (Storybook controls); every option is listed in the API section of this file.

API v1

Source: ./src/components/toggletip/v1/ToggletipV1.API.g.mdx

Toggletip v1 API

API: <oc-toggletip-v1> (ToggletipV1)

The toggletip component provides a small non-modal dialog box that displays additional contextual information or hints when clicking an interactive element. It supports custom content and offers configuration options for positioning either above or below the triggering element, as well as controlling its initial visibility state.

Attributes / properties
Attribute Type Default Required Description
sticky boolean false no Sets the sticky state of the toggletip. Set to true an opened toggletip always stays in viewport, even if the users scrolls the page. This attribute is automatically unset on closing the toggletip.
visible boolean false no Sets the visibility state of the toggletip. Set to true to show the toggletip. This attribute is automatically unset on closing the toggletip.
position "top" | "bottom" "top" no Sets the preferred position of the toggletip. The position can change depending on the available space.
oc-aria-label string no Sets the aria-label attribute of the toggletip content element. This is used to provide an accessible name for the toggletip content.
Slots
Slot Required Description
default yes Contains the interactive element that opens the toggletip.
Example:

<oc-link-v2>Text with toggletip.</oc-link-v2>
toggletip-content yes Contains the content of the toggletip itself.
Example:

<span slot="toggletip-content">This is the toggletip!</span>
Events
Event Detail type Description
oc-toggletip-open CustomEvent<void> Dispatched when the toggletip is being opened. May be cancelled to prevent the toggletip from opening.
oc-toggletip-close CustomEvent<void> Dispatched when the toggletip is being closed. May be cancelled to prevent the toggletip from closing.
oc-open CustomEvent<void> Dispatched when the toggletip is being opened. May be cancelled to prevent the toggletip from opening.
oc-close CustomEvent<void> Dispatched when the toggletip is being closed. May be cancelled to prevent the toggletip from closing.
oc-property-change OcToggletipV1Events["oc-property-change"] Whenever a property value changes, this event triggers. Use this event to track all property changes within the component.

Refer to the Events documentation for more information.
oc-mount { component: string; } 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.
Methods
  • recalcPosition: () => void

    Recalculates the position of the toggletip. This can be useful if the content of the toggletip changes dynamically, or if the position of the trigger element changes.

    Returns: void

    Example:

    const toggletip = document.querySelector("oc-toggletip-v1");
    toggletip.recalcPosition();
    
CSS custom properties
Custom property Default Description
--background-color undefined Sets the custom background color

Variations (v1)

Source: ./src/components/toggletip/v1/Variations.mdx

Variations

Listed below are the most common variations of the toggletip 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.

Default

Story: components-toggletip-variations--default · tags: components

The default configuration initially hides the toggletip and displays it below the trigger element.

Args: oc-aria-label=Einfacher Toggletip, defaultSlot=<oc-button-v1 oc-aria-label="Für mehr Informationen klicken">Dieser Text hat einen Toggletip!</oc-button-v1>, toggletipContentSlot=<span slot='toggletip-content'>Hier ist der Toggletip!</span>

<oc-toggletip-v1 oc-aria-label="Einfacher Toggletip">
  <span slot='toggletip-content'>Hier ist der Toggletip!</span>
  <oc-button-v1 oc-aria-label="Für mehr Informationen klicken">Dieser Text hat einen Toggletip!</oc-button-v1>
</oc-toggletip-v1>

Preferred position

Story: components-toggletip-variations--above · tags: components

Variation using the position attribute with the value top to set the preferred position of the toggletip above the trigger element. The toggletip may still be displayed either below or above the trigger element depending on the available space.

Args: oc-aria-label=Oberer Toggletip, defaultSlot=<oc-button-v1>Dieser Text hat einen Toggletip!</oc-button-v1>, toggletipContentSlot=<span slot='toggletip-content'>Hier ist der Toggletip!</span>, position=top, style=margin-top: 100px

<oc-toggletip-v1 oc-aria-label="Oberer Toggletip" position="top">
  <span slot='toggletip-content'>Hier ist der Toggletip!</span>
  <oc-button-v1>Dieser Text hat einen Toggletip!</oc-button-v1>
</oc-toggletip-v1>

Intially visible

Story: components-toggletip-variations--visible · tags: components

Variation with the visible attribute initially set to true, showing the toggletip without needing to click the trigger element.

Args: oc-aria-label=Sofort sichtbarer Toggletip, defaultSlot=<oc-button-v1>Dieser Text hat einen Toggletip!</oc-button-v1>, toggletipContentSlot=<span slot='toggletip-content'>Hier ist der Toggletip!</span>, visible=true

<oc-toggletip-v1 oc-aria-label="Sofort sichtbarer Toggletip" visible>
  <span slot='toggletip-content'>Hier ist der Toggletip!</span>
  <oc-button-v1>Dieser Text hat einen Toggletip!</oc-button-v1>
</oc-toggletip-v1>

Long text

Story: components-toggletip-variations--long-text · tags: components

Variation with long text, showcasing the maximum width of the toggletip.

Args: oc-aria-label=Toggletip mit langem Text, defaultSlot=<oc-button-v1>Dieser Text hat einen langen Toggletip!</oc-button-v1>, toggletipContentSlot=(see snippet)

<oc-toggletip-v1 oc-aria-label="Toggletip mit langem Text">
  <span slot='toggletip-content'>Der Text in diesem Toggletip ist sehr lang. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.</span>
  <oc-button-v1>Dieser Text hat einen langen Toggletip!</oc-button-v1>
</oc-toggletip-v1>

Sticky on screen

Story: components-toggletip-variations--sticky-on-screen · tags: components

Variation that sticks on screen, even when achnor is scrolled out of the viewport.

Args: visible=false, sticky=true, oc-aria-label=Toggletip, der im Viewport klebt, defaultSlot=<oc-button-v1>Toggletip, der festklebt.</oc-button-v1>, toggletipContentSlot=<span slot='toggletip-content'>Dieser Toggletip klebt im Viewport!</span>

<oc-toggletip-v1 sticky oc-aria-label="Toggletip, der im Viewport klebt">
  <span slot='toggletip-content'>Dieser Toggletip klebt im Viewport!</span>
  <oc-button-v1>Toggletip, der festklebt.</oc-button-v1>
</oc-toggletip-v1>

Custom Background Color

Story: components-toggletip-variations--custom-background-color · tags: components

Snackbar with action slot & custom background-color

Args: --background-color=var(--oc-semantic-color-background-strong-warm-red)

<div style="height: 150px; display: flex; align-items: center; justify-content: center">
  <!-- to allow space for toggletip -->
  <oc-toggletip-v1
    style="--background-color: var(--oc-semantic-color-background-strong-warm-red)"
    class="${className}"
    position="bottom"
    oc-aria-label="Toggletip auf einem Link"
  >
    <oc-button-v1 oc-aria-label="Für mehr Informationen klicken"
      >Dieser Text hat einen Toggletip!</oc-button-v1
    >
    <span slot="toggletip-content">Hier ist der Toggletip!</span>
  </oc-toggletip-v1>
</div>

${css}
Story source (TypeScript, verbatim from Storybook)
{
  name: "Custom Background Color",
  args: {
    "--background-color": "var(--oc-semantic-color-background-strong-warm-red)"
  },
  render({
    ...props
  }) {
    const {
      css,
      className
    } = cssVariablesExample(props);
    return html`
      <div style="height: 150px; display: flex; align-items: center; justify-content: center">
        <!-- to allow space for toggletip -->
        <oc-toggletip-v1
          ${spread(props)}
          class="${className}"
          position="bottom"
          oc-aria-label="Toggletip auf einem Link"
        >
          <oc-button-v1 oc-aria-label="Für mehr Informationen klicken"
            >Dieser Text hat einen Toggletip!</oc-button-v1
          >
          <span slot="toggletip-content">Hier ist der Toggletip!</span>
        </oc-toggletip-v1>
      </div>

      ${css}
    `;
  }
}

Demo: multiple toggletip configurations

Story: components-toggletip-variations--demo · tags: components

A Demo showcasing multiple toggletip configurations on different elements.

<div class="container">
  <oc-toggletip-v1 position="bottom" oc-aria-label="Toggletip auf einem Link">
    <oc-link-v2 as-button>Toggletip on a link</oc-link-v2>
    <span slot="toggletip-content">This toggletip is on the bottom of the element!</span>
  </oc-toggletip-v1>
  <oc-toggletip-v1 position="top" oc-aria-label="Toggletip auf einem Button">
    <oc-button-v1>Toggletip on a button</oc-button-v1>
    <span slot="toggletip-content">This toggletip is on the top of the element!</span>
  </oc-toggletip-v1>
  <oc-toggletip-v1 oc-aria-label="Toggletip auf einem Icon-Button">
    <oc-icon-button-v3
      icon="crab"
      oc-aria-label="Toggletip on an icon button"
    ></oc-icon-button-v3>
    <span slot="toggletip-content"
      >This toggletip contains
      <span style="font-weight: bold; font-style: italic">fancy</span> text styles!</span
    >
  </oc-toggletip-v1>
  <oc-button-v1>Button w/o Toggletip</oc-button-v1>
</div>
<div class="text-container">
  <p>Beispieltext mit eingebettetem Toggletip auf einfachem &lt;span&gt;:</p>
  <p>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in
    faucibus orci luctus et ultrices posuere cubilia curae; Integer posuere, nisi non
    hendrerit laoreet, velit sapien cursus felis, at suscipit nisl metus non justo. Sed
    efficitur, augue a dictum congue, purus lorem feugiat nibh, a malesuada augue lectus id
    nibh. Praesent id sem vitae nibh suscipit
    <oc-toggletip-v1 oc-aria-label="Toggletip auf einem Wort">
      <u>Löweneckerchen</u>
      <span slot="toggletip-content">Lerche</span>
    </oc-toggletip-v1>
    euismod. Curabitur quis tellus non neque dictum aliquet.
  </p>
</div>
<style>
  .container {
    padding: 50px 10px;
    width: 100%;
    max-width: 600px;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
  }
  .text-container {
    margin-top: 20px;
    padding: 20px;
    border: 1px solid #ccc;
    width: 100%;
    max-width: 600px;
  }
</style>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Demo: multiple toggletip configurations",
  argTypes: hideControlsBadge(Metadata),
  parameters: {
    controls: {
      disabled: true
    },
    docs: {
      story: {
        inline: false,
        height: "200px"
      }
    }
  },
  render() {
    return html`
      <div class="container">
        <oc-toggletip-v1 position="bottom" oc-aria-label="Toggletip auf einem Link">
          <oc-link-v2 as-button>Toggletip on a link</oc-link-v2>
          <span slot="toggletip-content">This toggletip is on the bottom of the element!</span>
        </oc-toggletip-v1>
        <oc-toggletip-v1 position="top" oc-aria-label="Toggletip auf einem Button">
          <oc-button-v1>Toggletip on a button</oc-button-v1>
          <span slot="toggletip-content">This toggletip is on the top of the element!</span>
        </oc-toggletip-v1>
        <oc-toggletip-v1 oc-aria-label="Toggletip auf einem Icon-Button">
          <oc-icon-button-v3
            icon="crab"
            oc-aria-label="Toggletip on an icon button"
          ></oc-icon-button-v3>
          <span slot="toggletip-content"
            >This toggletip contains
            <span style="font-weight: bold; font-style: italic">fancy</span> text styles!</span
          >
        </oc-toggletip-v1>
        <oc-button-v1>Button w/o Toggletip</oc-button-v1>
      </div>
      <div class="text-container">
        <p>Beispieltext mit eingebettetem Toggletip auf einfachem &lt;span&gt;:</p>
        <p>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in
          faucibus orci luctus et ultrices posuere cubilia curae; Integer posuere, nisi non
          hendrerit laoreet, velit sapien cursus felis, at suscipit nisl metus non justo. Sed
          efficitur, augue a dictum congue, purus lorem feugiat nibh, a malesuada augue lectus id
          nibh. Praesent id sem vitae nibh suscipit
          <oc-toggletip-v1 oc-aria-label="Toggletip auf einem Wort">
            <u>Löweneckerchen</u>
            <span slot="toggletip-content">Lerche</span>
          </oc-toggletip-v1>
          euismod. Curabitur quis tellus non neque dictum aliquet.
        </p>
      </div>
      <style>
        .container {
          padding: 50px 10px;
          width: 100%;
          max-width: 600px;
          display: flex;
          align-items: center;
          flex-wrap: wrap;
          gap: 10px;
        }
        .text-container {
          margin-top: 20px;
          padding: 20px;
          border: 1px solid #ccc;
          width: 100%;
          max-width: 600px;
        }
      </style>
    `;
  }
}

Interaction tests (ToggletipV1.interactions.stories.ts)

Interaction test stories (automated tests, not usage patterns).

Open and close toggletip with mouse

Story: components-toggletip-interaction-tests--open-and-close-with-mouse · tags: play-fn

Args: defaultSlot=<oc-link-v2 as-button>Link</oc-link-v2>, toggletipContentSlot=<div slot='toggletip-content'>Inhalt des Toggletips</div>

Story source (TypeScript, verbatim from Storybook)
{
  name: "Open and close toggletip with mouse",
  args: {
    defaultSlot: "<oc-link-v2 as-button>Link</oc-link-v2>",
    toggletipContentSlot: "<div slot='toggletip-content'>Inhalt des Toggletips</div>"
  },
  play: async ({
    canvasElement
  }) => {
    const toggletipContainer = canvasElement.getElementsByTagName("oc-toggletip-v1")[0];
    const activator = toggletipContainer.getElementsByTagName("oc-link-v2")[0]!.shadowRoot!.querySelector("span")!;

    // Not yet rendered
    let toggletip = toggletipContainer.shadowRoot?.querySelector("oc-popover-v1")?.shadowRoot?.querySelector(".popover-popover");
    await expect(toggletip).toBeFalsy();
    await userEvent.click(activator);
    waitFor(() => {
      toggletip = toggletipContainer.shadowRoot?.querySelector("oc-popover-v1")?.shadowRoot?.querySelector(".popover-popover");
      return expect(toggletip).not.toBeFalsy();
    });
    waitFor(() => {
      toggletip = toggletipContainer.shadowRoot?.querySelector("oc-popover-v1")?.shadowRoot?.querySelector(".popover-popover");
      return expect(toggletip).toHaveClass("visible");
    });
    const closeButton = toggletipContainer.shadowRoot?.querySelector("oc-popover-v1")?.shadowRoot?.querySelector("oc-icon-button-v3.close-button");
    await userEvent.click(closeButton!);
    await new Promise(res => {
      setTimeout(res, 500);
    });
    waitFor(() => {
      toggletip = toggletipContainer.shadowRoot?.querySelector("oc-popover-v1")?.shadowRoot?.querySelector(".popover-popover");
      // Should be removed from DOM
      return expect(toggletip).toBeFalsy();
    });
  }
}

Open and close toggletip with keyboard

Story: components-toggletip-interaction-tests--open-and-close-with-keyboard · tags: skip-test, play-fn

Args: defaultSlot=<oc-link-v2 as-button>Link</oc-link-v2>, toggletipContentSlot=<div slot='toggletip-content'>Inhalt des Toggletips</div>

Story source (TypeScript, verbatim from Storybook)
{
  name: "Open and close toggletip with keyboard",
  args: {
    defaultSlot: "<oc-link-v2 as-button>Link</oc-link-v2>",
    toggletipContentSlot: "<div slot='toggletip-content'>Inhalt des Toggletips</div>"
  },
  tags: ["skip-test"],
  // Skipped due to flakiness in both CI and local runs
  play: async ({
    canvasElement
  }) => {
    const toggletipContainer = canvasElement.getElementsByTagName("oc-toggletip-v1")[0];
    const activator = toggletipContainer.getElementsByTagName("oc-link-v2")[0];
    // Not yet rendered
    let toggletip = toggletipContainer.shadowRoot?.querySelector(".popover-popover");
    await expect(toggletip).toBeFalsy();
    activator.focus();
    await userEvent.keyboard("{Enter}");
    waitFor(() => {
      toggletip = toggletipContainer.shadowRoot?.querySelector("oc-popover-v1")?.shadowRoot?.querySelector(".popover-popover");
      return expect(toggletip).toHaveClass("visible");
    });
    await waitFor(() => {
      return expect(toggletipContainer.shadowRoot?.activeElement?.tagName).toEqual("OC-POPOVER-V1"); // dialog should be focused
    });
    await userEvent.keyboard("[Escape]");

    // Need longer then a tick, because we have some transitions and state updates before hiding
    await new Promise(res => {
      setTimeout(res, 500);
    });
    toggletip = toggletipContainer.shadowRoot?.querySelector("oc-popover-v1")?.shadowRoot?.querySelector(".popover-popover");
    await expect(toggletip).toBeFalsy();
    await waitFor(async () => [expect(activator?.shadowRoot?.activeElement?.tagName).toEqual("SPAN") // link button should be focused
    ]);
  }
}