View Source Sandboxing components

In PhxLiveStorybook your components live within the storybook, so they share some context with the storybook: styling and scripts.

While the original Storybook for React only relies on iframes, we find them quite slow and don't want them to be the default choice.

This guide will explain:

  • what JS context do your components share with the storybook?
  • how is the storybook styled, to prevent most styling clashes?
  • how should you provide the style of your components with scoped styles?
  • how to, as a last resort, enable iframe rendering?

1-what-js-context-do-your-components-share-with-the-storybook

1. What JS context do your components share with the storybook?

PhxLiveStorybook runs with Phoenix LiveView and therefore requires its LiveSocket. This LiveSocket is the same used by your components: you just need to inject it with your own Hooks, Params and Uploaders.

To do so, create a JS file that will declare your Hooks, Params and Uploaders and set them in window.storybook. This script will be loaded immediately before the storybook's script.

// assets/js/my_components.js
import * as Hooks from "./hooks";
import * as Params from "./params";
import * as Uploaders from "./uploaders";
(function () {
  window.storybook = { Hooks, Params, Uploaders };
})();

Then set the js_path: "/assets/js/components.js" option to the storybook within your config.exs file.

You can also use this script to inject whatever content you want into document HEAD, such as external scripts.

The Params will be available in page stories as connect_params assign. There is currently no way to access them in component or live component stories.

2-how-is-the-storybook-styled

2. How is the storybook styled?

PhxLiveStorybook is using TailwindCSS with preflight (which means all default HTML styles from your browser are removed) and a custom prefix: lsb- (which means that instead of using bg-blue-400 the storybook uses lsb-bg-blue-400).

Only elements with the .lsb class are preflighted, in order to let your component styling as-is.

So unless your components use lsb or lsb- prefixed classes there should be no styling leak from the storybook to you components.

3-how-should-you-provide-the-style-of-your-components

3. How should you provide the style of your components?

You need to inject your component's stylesheets into the storybook. Just (like for JS), set the css_path: "/assets/css/components.css" option in config.exs.

The previous part (2.) was about storybook styles not leaking into your components. This part is about the opposite: don't accidentally mess up Storybook styling with your styles.

All containers rendering your components in the storybook (stories, playground, pages ...) carry the .lsb-sandbox CSS class and a custom sandboxing class of your choice.

You can leverage this to scope your styles with this class. Here is how you can do it with TailwindCSS:

  • configure phx_live_storybook with a custom sandbox_class:
# lib/my_app_web/storybook.ex
defmodule MyAppWeb.Storybook do
  use PhxLiveStorybook,
    ...
    sandbox_class: "my-app-sandbox",
  • use Tailwind important selector strategy with this class. It will prefix all your tailwind classes increasing their specificity, hence their priority.
// assets/tailwind.config.js
module.exports = {
  // ...
  important: ".my-app-sandbox",
};
  • nest your custom styles under Tailwind @layer utilities. This way, your styling will also benefit from sandboxing.
/* assets/css/components.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
  /* this style will be generated as .my-app-sandbox :not(i) { ... } */
  /* we use :not(i) instead of * because we don't want to override FontAwesome icons styles */
  /* (some icons are indeed rendered with your own sandboxed styles) */
  :not(i) {
    font-family: "MyComponentsFont";
    @apply text-slate-600;
  }

  /* this style will be generated as .my-app-sandbox h1 { ... } */
  h1 {
    @apply text-2xl font-bold text-slate-700 mt-2 mb-6;
  }

  /* this style will be generated as .my-app-sandbox h2 { ... } */
  h2 {
    @apply text-xl font-bold text-slate-700 mt-2 mb-4;
  }
}

4-enabling-iframe-rendering

4. Enabling iframe rendering

As a last resort, if for whatever reason you cannot make your component live within the storybook (an example would be that your component needs to bind listeners on document), it is possible to enable iframe rendering, component per component.

Just add the iframe option to it.

# storybook/components/button.exs
defmodule MyAppWeb.Storybook.Components.Button do
 alias MyAppWeb.Components.Button
 use PhxLiveStorybook.Story, :component

 def function, do: &Button.button/1
 def container, do: :iframe

 # ...
end