Skip to main content

Writing Stories

Before flipbook can discover your Stories, you need a Storybook. A Storybook is any ModuleScript with a .storybook extension. It acts as the topmost configuration for each collection of Stories in your project.

Relatedly, a Story is any ModuleScript with a .story extension, typically parented as a sibling to the UI component it renders.

Stories are what you will be working with the most. The Storybook is simply what tells flipbook how to find them and render them.

Getting started

A Storybook can be parented anywhere in the experience. The only requirement is that it defines a storyRoots array so flipbook knows where to search for Stories.

The simplest Storybook looks like this:

ProjectName.storybook.luau
return {
storyRoots = {
script.Parent,
},
}

And here's an example of a Story that renders a TextButton:

Button.story.luau
return {
story = function()
local button = Instance.new("TextButton")
button.Text = "Button"
button.TextSize = 16
button.Font = Enum.Font.BuilderSansExtraBold
button.TextColor3 = Color3.fromRGB(50, 50, 50)
button.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
button.BorderSizePixel = 0
button.Size = UDim2.fromOffset(200, 40)

button.Activated:Connect(function()
print("clicked")
end)

return button
end,
}

In the flipbook plugin, opening the Button story will render out the component.

Rendering the Button story in flipbook's UI

From there, making changing to the Story will live-reload the rendered button.

To connect it back to Studio, these files could simply be stored in ReplicatedStorage as ModuleScripts like so: Screenshot of Studio showing the hierarchy of ReplicatedStorage

Using frameworks

By default, flipbook uses a function-based renderer with support for Roblox Instances to get you up and running. Simply returning an Instance allows flipbook to manage the creation and destruction of that Instance so you don't leak memory while working.

flipbook also has built-in support for UI libraries like React and Fusion. The full list can be seen on the Frameworks page.

You can tell flipbook to use a particular UI library by passing in the packages object. Here's an example with React:

ReactButton.story.luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local React = require(ReplicatedStorage.Packages.React)
local ReactRoblox = require(ReplicatedStorage.Packages.ReactRoblox)

return {
story = function()
return React.createElement("TextButton", {
Text = "Click Me",
TextSize = 16,
Font = Enum.Font.BuilderSansExtraBold,
TextColor3 = Color3.fromRGB(50, 50, 50),
BackgroundColor3 = Color3.fromRGB(255, 255, 255),
BorderSizePixel = 0,
Size = UDim2.fromOffset(200, 40),
[React.Event.Activated] = function()
print("clicked")
end,
})
end,
packages = {
React = React,
ReactRoblox = ReactRoblox,
},
}

It can be tedious to supply the packages object in each Story module, which is why it is more common to add them globally in the Storybook so that all Stories can render with the UI library you use across your project.

The following example splits out the body of the story to a ReactButton component and offloads the definition of packages to the Storybook:

ReactButton.luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local React = require(ReplicatedStorage.Packages.React)

local function ReactButton(props: {
text: string,
onActivated: () -> (),
})
return React.createElement("TextButton", {
Text = props.text,
TextSize = 16,
Font = Enum.Font.BuilderSansExtraBold,
TextColor3 = Color3.fromRGB(50, 50, 50),
BackgroundColor3 = Color3.fromRGB(255, 255, 255),
BorderSizePixel = 0,
Size = UDim2.fromOffset(200, 40),
[React.Event.Activated] = props.onActivated,
})
end

return ReactButton
ReactButton.story.luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local React = require(ReplicatedStorage.Packages.React)

local ReactButton = require(script.Parent.ReactButton)

return {
story = function()
return React.createElement(ReactButton, {
text = "Click Me",
onActivated = function()
print("click")
end,
})
end,
}
ReactProject.storybook.luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local React = require(ReplicatedStorage.Packages.React)
local ReactRoblox = require(ReplicatedStorage.Packages.ReactRoblox)

return {
name = "React",
storyRoots = {
script.Parent,
},
packages = {
React = React,
ReactRoblox = ReactRoblox,
},
}
tip

Stories can individually override the global packages so if you need to use another UI library for a particular Story, you can do that.