The TYPO3 extension storybook integrates Storybook into TYPO3 projects.
With the open-source tool Storybook, developers can develop and test UI components in isolation.
It provides an environment where components can be visually displayed and interactively tested.
The extension facilitates the development of TYPO3 frontend components by enabling seamless integration of Storybook into TYPO3.
- render TYPO3 Fluid components in Storybook
- configure stories inside storybook with the standard Storybook's CSF format
- automatically generates controls for your components arguments and slots
- Interactivity via Storybook's Controls, allowing you to change component properties in real-time
- shows Fluid code snippets for each component, allowing you to copy the code directly from Storybook
- wide range of Storybook's addons are available to enhance the development experience, such as accessibility checks, design systems, and more
The extension can be installed via Composer:
composer require andersundsehr/storybook --devit is recommended to install storybook into your projects package.json file as well:
# !!! make sure to install the same version as the extension !!!
npm install @andersundsehr/storybook-typo3 --save-dev
# or
yarn add @andersundsehr/storybook-typo3 --devset framework in your .storybook/main.ts file to @andersundsehr/storybook-typo3 as shown below.
After that you can run storybook with:
# run in dev mode:
./node_modules/.bin/storybook dev -p 6006
# or build it for production:
./node_modules/.bin/storybook build
# preview the production build:
npx http-server ./storybook-staticAlso See full installation steps in the Getting Started Guide.
To be able to render your own Fluid components in Storybook,
you need to configure your own ComponentCollection
and register the global fluid namespace in your ext_localconf.php file:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['my'] = Vendor\MyExtension\ComponentCollection::class;You need your own .storybook/main.ts file in your project, which should look like this:
import type { StorybookConfig } from '@andersundsehr/storybook-typo3';
const config: StorybookConfig = {
framework: '@andersundsehr/storybook-typo3', // required
stories: [
"../src/**/*.@(mdx|stories.@(mdx|js|jsx|mjs|ts|tsx))",
],
core: {
disableTelemetry: true,
},
env: (envs) => {
return {
STORYBOOK_TYPO3_ENDPOINT: 'http://localhost/_storybook/',
STORYBOOK_TYPO3_WATCH_ONLY_STORIES: '0', // set to '1' If you already use vite in your TYPO3 with HMR
// do not set your api key here! https://www.deployhq.com/blog/protecting-your-api-keys-a-quick-guide
...envs, // envs given to storybook have precedence
};
},
};
export default config;With a story file like this:
import { type Meta, type StoryObj, fetchComponent } from '@andersundsehr/storybook-typo3';
export default {
component: await fetchComponent('de:card'),
} satisfies Meta;
export const Pirate: StoryObj = {
args: {
title: 'Yar Pirate Ipsum',
text: "Prow scuttle parrel provost Sail ho shrouds spirits boom mizzenmast yardarm. Pinnace holystone mizzenmast quarter crow's nest nipperkin grog yardarm hempen halter furl. Swab barque interloper chantey doubloon starboard grog black jack gangway rutters.",
link: 'https://www.andersundsehr.com',
},
};and a Fluid component like this:
<f:argument name="title" type="string" description="The title of the card" />
<f:argument name="text" type="string" description="The main text of the card" />
<f:argument name="link" type="string" description="Typolink parameter" />
<a href="{f:uri.typolink(parameter: link)}" class="card">
<h1 class="card__title">{title}</h1>
<p class="card__text">{text}</p>
<span class="card__moreButton">more</span>
</a>
<f:asset.css identifier="de:card">
.card {
display: block;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
text-decoration: none;
color: inherit;
}
.card__title {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.card__text {
font-size: 1rem;
margin-bottom: 1rem;
}
.card__moreButton {
display: inline-block;
padding: 0.5rem 1rem;
background-color: #007bff;
color: white;
border-radius: 4px;
text-decoration: none;
}
</f:asset.css>you get a Storybook interface that allows you to interactively test your component, like this:
You can Select the site and the language in the top right corner of the Storybook interface:
Slot content of a story (slot____default, slot____<name>) is passed through as a sanitized string. With the extension setting renderSlotsAsFluid it is rendered as a Fluid template in the rendering context of the story instead, so a slot can hold another component:
export const WithCard: StoryObj = {
args: {
slot____default: '<de:card title="Yar Pirate Ipsum" link="https://www.andersundsehr.com" />',
},
};component() builds that tag from the story of the other component, so stories reference each other instead of repeating markup:
// .storybook/component.ts
import { createComponent } from '@andersundsehr/storybook-typo3';
export const component = createComponent(import.meta.glob('../Components/**/*.stories.ts'));
// Components/Page/Page.stories.ts
import { component } from '../../.storybook/component';
export const WithCard: StoryObj = {
args: { slot____default: await component('de:card', 'Pirate') },
};The setting is off by default: with it, story authors run arbitrary Fluid in the TYPO3 instance. See Slots in the Getting Started Guide.
https://docs.typo3.org/permalink/andersundsehr-storybook:start
- docs TODO link to hosted Storybook instance
- docs TODO link to playwright report?
The Storybook framework is located in the the-npm-package folder inside the extension.
It provides a custom framework for Storybook, which allows you to render TYPO3 Fluid components in Storybook.
It is implemented in TypeScript and is designed to be used with Storybook's Component Story Format (CSF).
The TYPO3 extension storybook uses a Middleware to handle requests to the Storybook endpoint.
It has Actions to give Storybook the necessary information.
It renders the Fluid components using the corresponding ComponentCollection that must be registered in TYPO3
via $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces'].
the test setup is a nice location to get started with developing the extension. it also has a dummy project that can be used to test the extension in a real TYPO3 environment.
Currently there are only integration tests with Playwright to run the tests you can use the following command:
cd .Build/
bash test.sh executeAllIf you want to make changes to the extension, and than run the tests again you should do this:
cd .Build/
bash test.sh composerInstall
bash test.sh storybookBuild
bash test.sh watchMode
# in different terminal:
cd .Build/
bash test.sh playwright:ui # (X11 forwarding required)After changing dependencies in the-npm-package/package.json or Documentation/dummy-project/package.json,
regenerate both package-lock.json files with the npm of the CI image:
cd .Build/
bash test.sh npmLockThe lockfile of the dummy project records the dependencies of the linked npm package, so it needs the vendor copy
of the extension; npmLock takes care of that. A lockfile written without it makes npm ci fail in CI.
Run npm for the-npm-package only through test.sh (buildTheNpmPackage, watchMode, npmLock), not on the host:
TypeScript 7 installs a native tsc binary for the platform of the last npm ci, so a host install and the container
install would overwrite each other's node_modules.
- Apple Silicon:
ghcr.io/pluswerk/php-devis published forlinux/amd64only, and under Rosetta its Apache does not come up (container testing-storybook-typo3-1 is unhealthy).test.shtherefore uses the multi-arch base imagewebdevops/php-apache-devon arm64 hosts automatically. Force one of the two withbash test.sh --amd64 …orbash test.sh --arm64 …; after switching runbash test.sh downso thetypo3container is recreated.
- Storybook 10:
@andersundsehr/storybook-typo3now builds on Storybook^10.6.5, which needs Node.js 22.12 or newer and loads.storybook/main.*as ESM (see the Storybook 10 migration notes). Update the npm package together with the extension.
This extension is licensed under the GPL-2.0-or-later license.
If something did not work 😮
or you appreciate this Extension 🥰 let us know.
We are always looking for great people to join our team! https://www.andersundsehr.com/karriere/