OTTODesign System

Code

Block

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

Version Tag Status API
v2 <oc-block-v2> Stable, allowed for generation BlockV2

Overview (v2)

Source: ./src/components/block/v2/Overview.mdx

Block

The block component provides a container for layout and spacing in your design. Use it to group content and control appearance through its properties.

Default variation

Story Default:

<oc-block-v2><oc-placeholder-v1 style="height: 100px;">DETACH TO REPLACE CONTENT</oc-placeholder-v1></oc-block-v2>

Configuration

Explore all available configuration options in the component configurator and see the changes affect the component in real-time.

Usage guidelines

Visit the variations page for examples of different block configurations and use cases.

Info

See the Block UX documentation for detailed user experience guidelines.

Accessibility

The block component is a presentational container and does not include interactive features. Ensure that any content placed inside the block is accessible and follows best practices for semantic HTML and ARIA attributes.

Visit the accessibility documentation for general guidelines on making your content accessible.

Configuration (v2)

Source: ./src/components/block/v2/Configuration.mdx

Block configuration

Configure the block 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-block-v2><oc-placeholder-v1 style="height: 100px;">DETACH TO REPLACE CONTENT</oc-placeholder-v1></oc-block-v2>

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

Migration (v2)

Source: ./src/components/block/v2/Migration.mdx

Migration from Block v1 to v2

The oc-block component has been updated from oc-block-v1 to oc-block-v2. This migration guide provides step-by-step instructions to update your project to the latest version.

Skip to:

What's new

Web component

The block is now a proper web component instead of a CSS-only class, providing:

  • Encapsulated styling
  • Consistent behavior across the application
  • Better integration with other Otto Components

API changes

Changed structure
v1 (CSS class) v2 (Web Component) Notes
<div class="oc-block-v1"> <oc-block-v2> Component-based

How to migrate

Instead of using a div with the oc-block-v1 class, use the oc-block-v2 component tag to create a block. The oc-block-v2 component provides a container for layout and spacing in your design. Use it to group content and control appearance through its properties.

Migrate a basic block

Replace the div with the oc-block-v1 class with the oc-block-v2 component tag.

<!-- From: -->
<div class="oc-block-v1">My Block</div>
<!-- To: -->
<oc-block-v2>My Block</oc-block-v2>

API v2

Source: ./src/components/block/v2/BlockV2.API.g.mdx

Block v2 API

API: <oc-block-v2> (BlockV2)

The block component provides a container for layout and spacing in your design. Use it to group content and control appearance through its properties.

Attributes / properties
Attribute Type Default Required Description
elevation-level "canvas" | "stacked" "canvas" no Sets the elevation level of the block.
padding "default" | "full-bleed" | "no-padding" "default" no Deprecated: Use CSS variables --custom-spacing-x and --custom-spacing-y instead.

Sets the padding of the block.
Slots
Slot Required Description
default yes Sets the main content of the block.
Events
Event Detail type Description
oc-property-change OcBlockV2Events["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.
CSS custom properties
Custom property Default Description
--oc-block-background-color undefined Sets a custom background color through a CSS variable.
--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.
--custom-spacing-x undefined Sets the custom horizontal spacing of the block content.
--custom-spacing-y undefined Sets the custom vertical spacing of the block content.

Variations (v2)

Source: ./src/components/block/v2/Variations.mdx

Variations

Listed below are the most common variations of the block 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-block-variations--default · tags: components, block, 2, variations

Default Component

Args: defaultSlot=<oc-placeholder-v1 style="height: 100px;">DETACH TO REPLACE CONTENT</oc-placeholder-v1>

<oc-block-v2><oc-placeholder-v1 style="height: 100px;">DETACH TO REPLACE CONTENT</oc-placeholder-v1></oc-block-v2>

Demo: Custom Background Color

Story: components-block-variations--demo-custom-background-color · tags: components, block, 2, variations

Demo: Custom Background Color

Args: defaultSlot=<oc-placeholder-v1 style="height: 100px;">DETACH TO REPLACE CONTENT</oc-placeholder-v1>

<style>
  .block-wrapper {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 16px;
    max-width: fit-content;
  }
</style>

<div class="block-wrapper">
  <oc-block-v2 style="--background-color: #B4E1D7">${unsafeHTML(placeholder)}</oc-block-v2>
  <oc-block-v2 style="--background-color: #C5EAFF">${unsafeHTML(placeholder)}</oc-block-v2>
  <oc-block-v2 style="--background-color: #DBFB98">${unsafeHTML(placeholder)}</oc-block-v2>
  <oc-block-v2 style="--background-color: #E5D5FE">${unsafeHTML(placeholder)}</oc-block-v2>
  <oc-block-v2 style="--background-color: #FFD7F5">${unsafeHTML(placeholder)}</oc-block-v2>
  <oc-block-v2 style="--background-color: #FEC5C6">${unsafeHTML(placeholder)}</oc-block-v2>
  <oc-block-v2 style="--background-color: #FFC5A5">${unsafeHTML(placeholder)}</oc-block-v2>
  <oc-block-v2 style="--background-color: #FFFAAF">${unsafeHTML(placeholder)}</oc-block-v2>
  <oc-block-v2 style="--background-color: #E5D5FE">${unsafeHTML(placeholder)}</oc-block-v2>
</div>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Demo: Custom Background Color",
  parameters: {
    controls: {
      disabled: true
    },
    chromatic: {
      hideInChromatic: true
    }
  },
  argTypes: hideControlsBadge(Metadata),
  args: {
    defaultSlot: placeholder
  },
  render() {
    return html`
      <style>
        .block-wrapper {
          display: grid;
          grid-template-columns: repeat(3, 1fr);
          gap: 16px;
          max-width: fit-content;
        }
      </style>

      <div class="block-wrapper">
        <oc-block-v2 style="--background-color: #B4E1D7">${unsafeHTML(placeholder)}</oc-block-v2>
        <oc-block-v2 style="--background-color: #C5EAFF">${unsafeHTML(placeholder)}</oc-block-v2>
        <oc-block-v2 style="--background-color: #DBFB98">${unsafeHTML(placeholder)}</oc-block-v2>
        <oc-block-v2 style="--background-color: #E5D5FE">${unsafeHTML(placeholder)}</oc-block-v2>
        <oc-block-v2 style="--background-color: #FFD7F5">${unsafeHTML(placeholder)}</oc-block-v2>
        <oc-block-v2 style="--background-color: #FEC5C6">${unsafeHTML(placeholder)}</oc-block-v2>
        <oc-block-v2 style="--background-color: #FFC5A5">${unsafeHTML(placeholder)}</oc-block-v2>
        <oc-block-v2 style="--background-color: #FFFAAF">${unsafeHTML(placeholder)}</oc-block-v2>
        <oc-block-v2 style="--background-color: #E5D5FE">${unsafeHTML(placeholder)}</oc-block-v2>
      </div>
    `;
  }
}

Demo: Stacked Block with background color

Story: components-block-variations--demo-stacked-block-with-background-color · tags: components, block, 2, variations

Demo: Stacked Block with background color

Args: defaultSlot=<oc-placeholder-v1 style="height: 100px;">DETACH TO REPLACE CONTENT</oc-placeholder-v1>

<oc-block-v2>
  <oc-block-v2 elevation-level="stacked" style="--background-color: #FFD7F5"
    >${unsafeHTML(placeholder)}</oc-block-v2
  >
</oc-block-v2>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Demo: Stacked Block with background color",
  parameters: {
    controls: {
      disabled: true
    },
    chromatic: {
      hideInChromatic: true
    }
  },
  argTypes: hideControlsBadge(Metadata),
  args: {
    defaultSlot: placeholder
  },
  render() {
    return html`
      <oc-block-v2>
        <oc-block-v2 elevation-level="stacked" style="--background-color: #FFD7F5"
          >${unsafeHTML(placeholder)}</oc-block-v2
        >
      </oc-block-v2>
    `;
  }
}

Demo: Stacked Block in Canvas Block

Story: components-block-variations--demo-stacked-block-in-canvas-block · tags: components, block, 2, variations

Demo: Stacked Block in Canvas Block

Args: defaultSlot=<oc-placeholder-v1 style="height: 100px;">DETACH TO REPLACE CONTENT</oc-placeholder-v1>

<oc-block-v2>
  <oc-block-v2 elevation-level="stacked">${unsafeHTML(placeholder)}</oc-block-v2>
</oc-block-v2>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Demo: Stacked Block in Canvas Block",
  parameters: {
    controls: {
      disabled: true
    },
    chromatic: {
      hideInChromatic: true
    }
  },
  argTypes: hideControlsBadge(Metadata),
  args: {
    defaultSlot: placeholder
  },
  render() {
    return html`
      <oc-block-v2>
        <oc-block-v2 elevation-level="stacked">${unsafeHTML(placeholder)}</oc-block-v2>
      </oc-block-v2>
    `;
  }
}

Demo: Stacked Block in Canvas Block with background color

Story: components-block-variations--demo-stacked-block-in-canvas-block-with-background-color · tags: components, block, 2, variations

Demo: Stacked Block in Canvas Block w/ background color

Args: defaultSlot=<oc-placeholder-v1 style="height: 100px;">DETACH TO REPLACE CONTENT</oc-placeholder-v1>

<oc-block-v2 style="--background-color: #FFD7F5">
  <oc-block-v2 elevation-level="stacked">${unsafeHTML(placeholder)}</oc-block-v2>
</oc-block-v2>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Demo: Stacked Block in Canvas Block with background color",
  parameters: {
    controls: {
      disabled: true
    },
    chromatic: {
      hideInChromatic: true
    }
  },
  argTypes: hideControlsBadge(Metadata),
  args: {
    defaultSlot: placeholder
  },
  render() {
    return html`
      <oc-block-v2 style="--background-color: #FFD7F5">
        <oc-block-v2 elevation-level="stacked">${unsafeHTML(placeholder)}</oc-block-v2>
      </oc-block-v2>
    `;
  }
}

Demo: Stacked Block with divider

Story: components-block-variations--demo-stacked-block-with-divider · tags: components, block, 2, variations

Demo: Stacked Block with divider

Args: defaultSlot=<oc-placeholder-v1 style="height: 100px;">DETACH TO REPLACE CONTENT</oc-placeholder-v1>

<oc-block-v2 style="max-width: 400px;">
  <oc-block-v2
    elevation-level="stacked"
    style="--custom-spacing-y: 16px; --custom-spacing-x: 0;"
  >
    ${unsafeHTML(placeholder)}
    <div style="height: 8px;"></div>
    <oc-divider-v1></oc-divider-v1>
    <div style="height: 8px;"></div>
    ${unsafeHTML(placeholder)}
  </oc-block-v2>
</oc-block-v2>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Demo: Stacked Block with divider",
  parameters: {
    controls: {
      disabled: true
    },
    chromatic: {
      hideInChromatic: true
    }
  },
  argTypes: hideControlsBadge(Metadata),
  args: {
    defaultSlot: placeholder
  },
  render() {
    return html`
      <oc-block-v2 style="max-width: 400px;">
        <oc-block-v2
          elevation-level="stacked"
          style="--custom-spacing-y: 16px; --custom-spacing-x: 0;"
        >
          ${unsafeHTML(placeholder)}
          <div style="height: 8px;"></div>
          <oc-divider-v1></oc-divider-v1>
          <div style="height: 8px;"></div>
          ${unsafeHTML(placeholder)}
        </oc-block-v2>
      </oc-block-v2>
    `;
  }
}

Demo: Custom spacings

Story: components-block-variations--demo-custom-spacings · tags: components, block, 2, variations

Demo of a block component containing a custom layout with adjustable sliders for custom spacings.

<div
  style="display: flex; border: 1px solid var(--oc-base-color-gray-200); width: fit-content; "
>
  <oc-block-v2>
    <ofc-placeholder-v1 variant="image" style="height: 126px"></ofc-placeholder-v1>
    <h1 class="oc-headline-200 oc-mt-100">Some example content</h1>
    <p class="oc-copy-100">
      Text Lorem Ipsum Dolor Sit Amet Bla bla bla. Und so zerbröselt der Keks nunmal.
    </p>
  </oc-block-v2>
</div>

<div style="display: flex; flex-direction: column; gap: 16px;  margin-top: 32px;">
  <div style="display: flex; gap: 16px; align-items: center;">
    <label for="horizontal-slider">Horizontal Spacing</label>
    <input
      type="range"
      name="horizontal-slider"
      id="horizontal-slider"
      min="0"
      max="64"
      value="0"
    />
    <span id="horizontal-slider-value">0px</span>
  </div>
  <div style="display: flex; gap: 16px; align-items: center;">
    <label for="vertical-slider">Vertical Spacing</label>
    <input
      type="range"
      name="vertical-slider"
      id="vertical-slider"
      min="0"
      max="64"
      value="16"
    />
    <span id="vertical-slider-value">16px</span>
  </div>
</div>

<script>
  (() => {
    const row = document.querySelector("oc-block-v2");
    const horizontalSlider = document.getElementById("horizontal-slider");
    const horizontalSliderValue = document.getElementById("horizontal-slider-value");
    const verticalSlider = document.getElementById("vertical-slider");
    const verticalSliderValue = document.getElementById("vertical-slider-value");

    horizontalSlider.addEventListener("input", (event) => {
      const value = event.target.value + "px";
      row.style.setProperty("--custom-spacing-x", value);
      horizontalSliderValue.textContent = value;
    });

    verticalSlider.addEventListener("input", (event) => {
      const value = event.target.value + "px";
      row.style.setProperty("--custom-spacing-y", value);
      verticalSliderValue.textContent = value;
    });
  })();
</script>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Demo: Custom spacings",
  parameters: {
    controls: {
      disabled: true
    },
    backgrounds: {
      value: "frame"
    },
    chromatic: {
      hideInChromatic: true
    }
  },
  render: () => {
    return html`
      <div
        style="display: flex; border: 1px solid var(--oc-base-color-gray-200); width: fit-content; "
      >
        <oc-block-v2>
          <ofc-placeholder-v1 variant="image" style="height: 126px"></ofc-placeholder-v1>
          <h1 class="oc-headline-200 oc-mt-100">Some example content</h1>
          <p class="oc-copy-100">
            Text Lorem Ipsum Dolor Sit Amet Bla bla bla. Und so zerbröselt der Keks nunmal.
          </p>
        </oc-block-v2>
      </div>

      <div style="display: flex; flex-direction: column; gap: 16px;  margin-top: 32px;">
        <div style="display: flex; gap: 16px; align-items: center;">
          <label for="horizontal-slider">Horizontal Spacing</label>
          <input
            type="range"
            name="horizontal-slider"
            id="horizontal-slider"
            min="0"
            max="64"
            value="0"
          />
          <span id="horizontal-slider-value">0px</span>
        </div>
        <div style="display: flex; gap: 16px; align-items: center;">
          <label for="vertical-slider">Vertical Spacing</label>
          <input
            type="range"
            name="vertical-slider"
            id="vertical-slider"
            min="0"
            max="64"
            value="16"
          />
          <span id="vertical-slider-value">16px</span>
        </div>
      </div>

      <script>
        (() => {
          const row = document.querySelector("oc-block-v2");
          const horizontalSlider = document.getElementById("horizontal-slider");
          const horizontalSliderValue = document.getElementById("horizontal-slider-value");
          const verticalSlider = document.getElementById("vertical-slider");
          const verticalSliderValue = document.getElementById("vertical-slider-value");

          horizontalSlider.addEventListener("input", (event) => {
            const value = event.target.value + "px";
            row.style.setProperty("--custom-spacing-x", value);
            horizontalSliderValue.textContent = value;
          });

          verticalSlider.addEventListener("input", (event) => {
            const value = event.target.value + "px";
            row.style.setProperty("--custom-spacing-y", value);
            verticalSliderValue.textContent = value;
          });
        })();
      </script>
    `;
  },
  argTypes: hideControlsBadge(Metadata)
}