Sign inSign up
Setup
Interaction tests
Publish
Composition

Viewports for responsive UIs with breakpoints

ℹ️  This page documents viewports using the modes API. Learn how to get started.

If you are transitioning from the chromatic.viewports API to the modes API, please consult the migration guide.

If you are using Chromatic with Playwright, please refer to the Playwright documentation.

If you are using Chromatic with Cypress, please refer to the Cypress documentation.

Define viewport modes

Modes are defined in the .storybook/modes.js file. If your project doesn’t have this file yet, go ahead and create it. To set viewport in a mode, specify the screen width and/or height using the chromatic[<your-mode-name>].viewport parameter.

The following are all acceptable viewport values:

  • Integer (which defaults to width)
  • Width & height (integer values only)
  • String that is integer or string integer with px suffix, e.g.: '1000px'
  • Only width (snapshot will be trimmed to the content height)
  • Only height (snapshot will use the default width of 1200px, and be trimmed to the content width)
// .storybook/modes.js

export const allModes = {
  default: {
    // integer is just width
    viewport: 1280,
  },
  specificBoth: {
    // object can specify both
    viewport: {
      height: 300,
      width: 800,
    },
  },
  specificWidth: {
    // object with width
    viewport: {
      width: 800,
    },
  },
  specificHeight: {
    // object with height
    viewport: {
      height: 800,
    },
  },
  specificString: {
    // string values
    viewport: {
      height: "600",
      width: "800px",
    },
  },
};

Apply modes to set viewports

Modes can be applied at different levels: project, component, or story. When a mode includes a valid viewport parameter, Chromatic will adjust the viewport size to match the defined dimensions while capturing the snapshot.

For example, given the following set of modes in .storybook/modes.js.

// .storybook/modes.js

export const allModes = {
  small: { name: "Small", styles: { width: "640px", height: "900px" } },
  medium: { name: "Medium", styles: { width: "768px", height: "900px" } },
  large: { name: "Large", styles: { width: "1024px", height: "900px" } },
};

We can apply the modes, like so:

// ArticleCard.stories.js

import { allModes } from "../.storybook/modes";
import { ArticleCard } from "./ArticleCard";

export default {
  component: ArticleCard,
  title: "ArticleCard",
  parameters: {
    chromatic: {
      //🔶 Test each story for ArticleCard in two modes
      modes: {
        mobile: allModes["small"],
        desktop: allModes["large"],
      },
    },
  },
};

export const Base = {
  args: {
    //...
  },
};
export const MembersOnly = {
  args: {
    //...
  },
};

When Chromatic captures your story, it will create two snapshots on your build, with the browser set at each viewport. These modes will be treated separately, with independent baselines and distinct approvals.

Combining modes with viewports addon

The Storybook viewport addon enables you to adjust the dimensions of the story canvas. Developers use it to verify the responsive behavior of components when building UIs. With modes, you can easily reference the different viewport sizes that you have configured for the addon.

Reference viewport by name

You start by configuring your desired set of viewports in .storybook/preview.js. For example:

⚠️  While the viewport addon allows you to specify dimensions using any valid CSS unit (such as px, rem, calc, etc.), Chromatic modes only support whole numbers or strings with a “px” suffix.

// .storybook/preview.js

const preview = {
  parameters: {
    viewport: {
      viewports: {
        xsm: { name: "XSmall", styles: { width: "320px", height: "900px" } },
        sm: { name: "Small", styles: { width: "640px", height: "900px" } },
        md: { name: "Medium", styles: { width: "768px", height: "900px" } },
        lg: { name: "Large", styles: { width: "1024px", height: "900px" } },
        xl: { name: "XL", styles: { width: "1280px", height: "900px" } },
        "2xl": { name: "2XL", styles: { width: "1536px", height: "900px" } },
      },
    },
  },
};

export default preview;

You can now refer to these viewports by their key in your modes definition. For example:

// .storybook/modes.js

export const allModes = {
  xsm: {
    viewport: "xs",
  },
  md: {
    viewport: "md",
  },
  xl: {
    viewport: "xl",
  },
  // Note, you can still specify the more
  // specific options listed in the section above
  specific: {
    viewport: {
      height: 300,
      width: 800,
    },
  },
};

Using defaultViewport

Within Storybook, you have the ability to configure the default viewport for stories at different levels: project, component, or story. This can be done by setting the parameters.viewport value. By adjusting this setting, you can control the dimensions of the story canvas when viewing it in the browser using Storybook. Chromatic will respect the defaultViewport setting when capturing snapshots.

defaultViewport can ve set via a named viewport from the viewport addon:

// .storybook/preview.js
const preview = {
  viewport: {
    viewports: {
      small: {
        name: "small",
        styles: {
          width: "375px",
          height: "375px",
        },
      },
    },
  },
};

That said, there are a few exceptions when Chromatic will ignore the defaultViewport setting:

  1. If your story has a chromatic.viewport parameter.
  2. If your story uses a mode that specifies viewport.
  3. defaultViewport is set to a non-pixel value, eg: 50%, 65em, calc(100vh - calc(70% + 2w)), etc.

In the example below, both stories will use the md viewport size when viewed in the browser. However, FirstStory will have one snapshot captured using the md viewport size, while SecondStory will have two snapshots captured at lg and xl viewport sizes respectively.

// MyComponent.stories.jsx

import type { Meta, StoryObj } from '@storybook/react';
import { allModes } from '../.storybook/modes';
import { MyComponent } from './MyComponent';

const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
  title: 'MyComponent',
};

export default meta;
type Story = StoryObj<typeof MyComponent>;

export const FirstStory: Story = {
  parameters: {
    viewport: {
      defaultViewport: 'md',
    },
  },
};

export const SecondStory: Story = {
  parameters: {
    viewport: {
      defaultViewport: 'md',
    },
    chromatic: {
      modes: {
        lg: allModes['lg'],
        xl: allModes['xl'],
      },
    },
  },
};

Migration from viewports (legacy) to modes

The new modes API is a successor to the viewport feature and takes it a step further. With Modes, you can test your stories in different viewports and any combination of global settings you define. Additionally, you can specify specific viewport heights for tests.

Currently, we will continue to support both APIs, but our plan is to deprecate the viewport feature. Behind the scenes, Chromatic will automatically convert viewports to modes when capturing a snapshot. If you are currently using the viewports feature, now is a good time to migrate to the new modes API.

Can I use Viewports and Modes simultaneously?

No, Chromatic will throw an error if you use both. Additionally, if you include the “viewports” key, Chromatic will convert each entry in the array into a separate mode.

For example, the following will be converted into two modes: 320px and 1200px

parameters: {
  chromatic: { viewports: [320, 1200] },
}

Frequently asked questions

Are there any constraints on the viewport size that I can choose?

A width or height can be any whole number between 200 and 2560 pixels. The maximum number of pixels per snapshot is 25,000,000.

Can I control the height of the viewport?

Yes, you can control the height using the viewport.height property and enabling parameters.chromatic.cropToViewport.

  • If intrinsic height of the root container is greater than the specified viewport.height then the snapshot will be clipped to viewport.height.
  • If intrinsic height of the root container is less than the specified viewport.height then the snapshot will be trimmed to that intrinsic height.

If no height is specified, Chromatic will capture a snapshot based on the intrinsic height of the root container.

// MyComponent.stories.js

import { MyComponent } from "./MyComponent";

export default {
  component: MyComponent,
  title: "MyComponent",
  parameters: {
    chromatic: {
      cropToViewport: true,
      modes: {
        small: {
          viewport: {
            height: 300,
            width: 800,
          },
        },
      },
    },
  },
};
How do I assign viewports globally to all components in my Storybook?

We don’t recommend this in most cases because each viewport is treated independently and snapshots must be approved as such.

But if you really want to assign project level modes, you can do so by setting the chromatic.modes parameter in .storybook/preview.js:

// .storybook/preview.js

import { allModes } from "../.storybook/modes";

const preview = {
  parameters: {
    chromatic: {
      modes: {
        light: allModes["light"],
        dark: allModes["dark"],
      },
    },
  },
};

export default preview;
What happens when I don’t specify a viewport?

Chromatic defaults to a viewport of width 1200px and height 900px.

How does snapshot cropping work with viewport width and height?

When you add a viewport, Chromatic will size the browser’s viewport to the defined width and height. It will then take a snapshot and crop it to the bounding box of the component. This eliminates negative space around snapshots, reducing the visual information you must review.

By default, Chromatic captures the full height of the rendered UI, even if a viewport height has been set. This is because in most cases, you want to capture the entire rendered UI. To restrict the capture height to the specified height, set parameters.chromatic.cropToViewport to true.