Changelog
-
Added a section about the new
Playgroundclass.
When building websites, I often create reusable UI components as Kirby snippets. In fact, it’s the first thing I do when starting a project. After that, I can simply play LEGO™ with my components.
Testing elements with different (worst case) content variants is key to creating a robust UI. For a long time, I used a special test template and put everything in there. You can find an example of that in the wild here: smart-transition.com/test
Depending on the project, these tests can get quite complex:

I also always have a “typography” test, with different formatting and heading sizes. This is especially helpful for testing fluid font sizes, the handling of worst-case word lengths or custom focus outlines.
My plugin
For a recent project, I had some time to develop a better solution. I created a simple plugin that provides a dedicated /playground route for component testing.
How it works
Here’s a simplified example to demonstrate how it works (mostly to show off the beautiful code highlighting thanks to Bogdan Condorachi’s Code Highlighter plugin).
Create a test in site/snippets/playground/button.php:
<div class="button-test">
<button class="button button--primary">Primary Button</button>
<button class="button button--secondary">Secondary Button</button>
<button class="button" disabled>Disabled Button</button>
<button class="button">Button with a super long label that will wrap on small screens</button>
</div>
Optionally add test-specific CSS in assets/css/playground/button.css:
.button-test {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
Now you can access playground/button to see your test.
The plugin automatically creates virtual pages for snippets in the playground folder and loads matching CSS files (optional). If you access /playground directly or visit a non-existing test, a list of links to available tests is returned. I also included optional authentication so only logged-in Panel users can access the tests.
Under the hood, the plugin registers a route that creates Page objects on-the-fly with new Page([...]) – no actual content files needed. It then visits the page with site()->visit() and the template includes the matching snippet.
It’s probably a bit too custom-tailored for my use-case, adding head and bottom snippets to the template. If I find a simple way to make this configurable, I will update the plugin.
Placeholder content
Testing components requires placeholder content. To spare you from writing Lorem ipsum by hand or hunting for placeholder images, the plugin provides a Playground class with helper methods.
Text
Generate placeholder text with Playground::text():
// "Lorem ipsum adipiscing dolor sit amet consectetur. Elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
Playground::text()
// "Lorem ipsum dolor sit amet."
Playground::text(['words' => 5])
// "Amet dolor sit" or similar, without "Lorem" or "."
Playground::text(['words' => 3, 'prefix' => false, 'suffix' => false])
// "<p>Lorem ipsum...</p>" – opt-in to wrap in <p> tags
Playground::text(['paragraphs' => 1])
// Add <a>, <strong>, <em> – number or [min, max] range
Playground::text(['links' => 2, 'bold' => [1, 3], 'italic' => true])
The words are shuffled, so each call produces different content like “Adipiscing dolor amet sit consectetur” or “Do consectetur sit elit”. Internally, it’s just a hardcoded string of lorem ipsum words that gets split, shuffled, and reassembled with random sentence breaks.
Images
Placeholder images have always been tricky for me. Where do you store them? In the site content? It just doesn’t feel right. The solution: images are fetched from picsum.photos and returned as virtual file objects:
// Seeded image (cached in media folder)
Playground::image(['seed' => 'hero', 'width' => 1200, 'height' => 800])
// With content fields
Playground::image(['content' => ['caption' => 'My caption']])
For multiple images there’s Playground::images() which returns a files collection:
Playground::images(6)
I wanted a solution that is as close as possible to real files so testing is reliable.
The trick was extending Kirby\Cms\File to override where the files live. Images are downloaded once and cached in the media folder. A custom route handles thumb generation so Kirby’s resize/crop methods just work.
Putting it together
Here’s how you’d test a “worst case” card component:
// site/snippets/playground/card.php
use Medienbaecker\Playground;
snippet('components/card', [
'image' => Playground::image(['seed' => 'worst-case'])->thumb('card'),
'title' => Playground::text(['words' => [15, 20], 'suffix' => false]),
'text' => Playground::text(['words' => [50, 80], 'bold' => [2, 5]]),
]);
A better way to test
Instead of cramming all test cases into a single template, I now have a clean, organized space to experiment with components. It’s still simple enough, making it a good fit for my website projects.
Do you use something similar? Let me know on Mastodon.
Replies
-
@Thomas Günther @getkirby I built something similar but with a slightly different setup. I should blog about it … just need a spot of spare time 🫣
-
@flokosiol @getkirby Ah, nice! Let me know when you had to time to blog about it. I'm sure I can learn something from your approach 🙌
-
@Thomas Günther @getkirby Already started but … it will take some time. Too much to write about. But I will keep at it. Thanks for motivating! 🙌
-
@Thomas Günther Very nice! 😍