Dominik Süß

fighting computers since 1999

in 

Storygleam

Generate UI stories while staying in the comfort of gleam


storygleam is a gleam package that allows you to test UI components using storybook without having to write a single line of JavaScript!

It is published to hex.pm and you can try it out today!

During this years December Adventure, I tried out gleam to build a finance tracking app for our parkour community. It was my first time working with gleam and I learned a lot of new things and am very happy to be doing some functional programming again! During this endeavor, the talks from Lambda Days 2025 got uploaded to youtube. One of the talks, Testing functional UIs by Hayleigh Thompson inspired me to explore UI testing further. While it specifically mentions fable, this didn't work for me as I'm not actually using lustre for its client side capabilities (only for straight forward html rendering).

To still test out my components, I set out on building storygleam!

How to use it?

Let's go through an example of how storygleam works and the issues it solves. We'll first need a new gleam project so let's initialize one and build a new component:

mkdir example-project
cd example-project
gleam new .
// in src/components/greeter.gleam
pub fn component(name: String) -> String {
  "<h1>Hello "
  <> case name {
    "" -> "you"
    n -> "<strong>"<> n <>"</strong>"
  }
  <> "!</h1>"
}

You could use something like gleeunit to test that the output of the function matches our expectations but this doesn't verify if the component looks good. For this, you need to render it in a browser and feed it the correct data.

If you're putting together components, chances are good that you're building a web application anyways so you could start your web server and find places in your application where this component is used to see how it looks. However, as the number of components grow, this can become quite annoying to do, especially if you need to try out the component with different combinations of states and inputs.

For this, a UI sandbox like storybook comes in handy. It allows you to display your components in isolation and gives you controls to see how different arguments map to different outputs.

But how do you use this with gleam? This is where storygleam comes in! It allows you to configure your storybook in gleam so you don't have to mess arround with gleam/js interoperability as I've already done this for you!

To begin using storygleam, add it to your dev dependencies:

gleam add --dev storygleam@1

Then call storygleam in the main function of your test module:

// in test/example_project_test.gleam
import storygleam

pub fn main() -> Nil {
  storygleam.run([], storygleam.default_preview())
}

You'll then need to initialize it by calling gleam test init once. This create a package.json file containing the required dependencies and build scripts. You only need to do this once.

After the package.json has been created, run pnpm install to install the javascript dependencies. Run pnpm dev to start developing. This does two things:

  • it starts the storybook development server
  • it watches your gleam files and recompiles them on change

Your browser should now open the storybook UI (go to localhost:6006 if it doesn't) but you'll be greeted by the following:

Oh no! Your Storybook is empty. This can happen when:

  • Your stories glob configuration does not match any files.
  • You have no stories defined in your story files.

That's because there are no stories defined yet! Let's fix that by filling that ominous empty list in storygleam.run:

// in test/example_project_test.gleam
import storygleam.{Meta, Story, StoryCollection}

pub fn main() -> Nil {
  storygleam.run(
    [
      StoryCollection(
        meta: Meta(
          id: "greeter",
          title: "Components/Greeter",
          args: [storygleam.StringArg("name", "")],
          render_fn: "component",
        ),
        stories: [
          Story(name: "Empty", args: [storygleam.StringArg("name", "")]),
        ],
        module_path: "example_project/components/greeter",
      ),
    ],
    storygleam.default_preview(),
  )
}

This tells storygleam where to find your component and which arguments it needs.

In your browser, you should now see the new story and the associated controls!

If you're using lustre to build your applications, you can instead use LustreMeta to work on exposed components instead. A full example of this can be found in the storygleam repo.

While this works for simple components, there's still a lot for me to work on to make this suitable for larger applications. I'll continue working on this on the side as I'm using it myself for the finance tracking thingy mentioned in the beginning.

If you encounter any issues with this or would like to contribute, the source code is available on codeberg and tangled. Send me a PR or open an issue in either of those and I'll try to respond in a reasonable time (no promises made 🤞).