1. Add the runtime
npm install livingblocksOr as a plain script, then start it on the page:
<script src="https://cdn.jsdelivr.net/npm/livingblocks@0.4/dist/livingblocks.min.js"></script>
<script>LivingBlocks.start(document.body)</script>Explore uxdeck
Build with uxdeck
See it live
How to use uxdeck
Recent files
Your account
LivingBlocks · v0.4.1 · MIT
A carousel, an accordion or a ring, written as data: layers with roles, the parts its behaviour needs, its settings. Any design draws it in its own style; a small runtime makes it work, with a keyboard and a screen reader. Free for everyone.
npm install livingblocks
Picture 1
A line about this picture.

Picture 2
A line about this picture.

Picture 3
A line about this picture.
This carousel: data-living='{"kind":"carousel","v":1,"settings":{"perView":1,"loop":true,"autoplay":false,"interval":5,"arrows":true,"dots":true,"swipe":true,"start":0,"parallax":0.12}}'
The idea
A Block is a file of data. A design draws it in its own style and marks the parts; the runtime reads those marks and makes it work. Nothing in the file runs.
blocks/faq.json in the package: layers with roles, the parts the behaviour needs, its settings. No code.
{
"$schema": "../schema/block.schema.json",
"format": 1,
"id": "faq",
"kind": "accordion",
"label": "FAQ list",
"hint": "A plain list of questions and answers between hairlines, all closed until opened",
"category": "content",
"isNew": true,
"settings": {
"single": false,
"start": -1
},
"root": {
"type": "stack",
"name": "FAQ list",
"w": 640,
"h": 520,
"layout": {
"direction": "column",
"gap": 0,
"pad": [0, 0, 0, 0],
"align": "stretch"
},
"sizeW": "fixed",
"sizeH": "fit",
"children": [
{
"type": "stack",
"name": "Item {n}",
"part": "item",
"w": 640,
"h": 124,
"repeat": 4,
"layout": {
"direction": "column",
"gap": 0,
"pad": [0, 0, 0, 0]
},
"sizeW": "fill",
"sizeH": "fit",
"clip": true,
"children": [
{
"type": "stack",
"name": "Header",
"part": "header",
"w": 640,
"h": 60,
"layout": {
"direction": "row",
"gap": 16,
"pad": [16, 0, 16, 0],
"align": "center",
"justify": "space-between"
},
"sizeW": "fill",
"sizeH": "fit",
"children": [
{
"type": "text",
"name": "Question",
"w": 540,
"h": 24,
"sizeW": "fill",
"text": "Question {n}",
"ink": "ink",
"typography": {
"role": "heading",
"size": 17,
"weight": 600
}
},
{
"type": "text",
"name": "Icon",
"part": "icon",
"w": 24,
"h": 28,
"text": "+",
"ink": "ink",
"typography": {
"role": "body",
"size": 28,
"weight": 300,
"align": "center"
}
}
]
},
{
"type": "stack",
"name": "Panel",
"part": "panel",
"w": 640,
"h": 64,
"layout": {
"direction": "column",
"gap": 0,
"pad": [0, 0, 20, 0]
},
"sizeW": "fill",
"sizeH": "fit",
"children": [
{
"type": "text",
"name": "Answer",
"w": 600,
"h": 44,
"sizeW": "fill",
"text": "The answer to question {n}, in a sentence or two.",
"ink": "ink",
"typography": {
"role": "body",
"size": 15,
"weight": 400
}
}
]
},
{
"type": "frame",
"name": "Divider",
"w": 640,
"h": 1,
"sizeW": "fill",
"sizeH": "fixed",
"fill": "surface"
}
]
}
]
}
}Drawn in a design: its settings in data-living, its parts in data-living-part. This is the HTML running in the third tab.
<div class="lbly">
<div class="lbly-0" data-living="{"kind":"accordion","v":1,"settings":{"single":false,"start":-1}}" role="group" aria-label="FAQ list">
<div class="lbly-1" data-living-part="item">
<div class="lbly-2" data-living-part="header">
<p class="lbly-3">Question 1</p>
<p class="lbly-4" data-living-part="icon">+</p>
</div>
<div class="lbly-5" data-living-part="panel">
<p class="lbly-6">The answer to question 1, in a sentence or two.</p>
</div>
<div class="lbly-7">
</div>
</div>
<div class="lbly-8" data-living-part="item">
<div class="lbly-9" data-living-part="header">
<p class="lbly-10">Question 2</p>
<p class="lbly-11" data-living-part="icon">+</p>
</div>
<div class="lbly-12" data-living-part="panel">
<p class="lbly-13">The answer to question 2, in a sentence or two.</p>
</div>
<div class="lbly-14">
</div>
</div>
<div class="lbly-15" data-living-part="item">
<div class="lbly-16" data-living-part="header">
<p class="lbly-17">Question 3</p>
<p class="lbly-18" data-living-part="icon">+</p>
</div>
<div class="lbly-19" data-living-part="panel">
<p class="lbly-20">The answer to question 3, in a sentence or two.</p>
</div>
<div class="lbly-21">
</div>
</div>
<div class="lbly-22" data-living-part="item">
<div class="lbly-23" data-living-part="header">
<p class="lbly-24">Question 4</p>
<p class="lbly-25" data-living-part="icon">+</p>
</div>
<div class="lbly-26" data-living-part="panel">
<p class="lbly-27">The answer to question 4, in a sentence or two.</p>
</div>
<div class="lbly-28">
</div>
</div>
</div>
</div>LivingBlocks.start() reads those attributes and makes it work: keyboard, screen reader, reduced motion.
Question 1
+
The answer to question 1, in a sentence or two.
Question 2
+
The answer to question 2, in a sentence or two.
Question 3
+
The answer to question 3, in a sentence or two.
Question 4
+
The answer to question 4, in a sentence or two.
Every behaviour
Each shown with one of the package’s Blocks. The controls are made from the checker’s own ranges, so none of them offers a value a Block cannot hold; the attribute under them is what the page carries.
Swipe, use the arrows, or the arrow keys when it has focus.

Product 1
€49

Product 2
€49

Product 3
€49

Product 4
€49

Product 5
€49

Product 6
€49
Settings Products
data-living='{"kind":"carousel","v":1,"settings":{"perView":3,"loop":false,"autoplay":false,"interval":5,"arrows":true,"dots":true,"swipe":true,"start":0,"parallax":0}}'Roles, not colours
A Block names its colours and type by role: the accent, the surface, the ink, the heading. Pick a design: the same three Blocks take its look, still working, and only its roles change.
Our opening hours change this week. See the new times before you visit.
×
The calmest hour of my week. I leave lighter every time.

Person 1
Member since 2024
The calmest hour of my week. I leave lighter every time.

Person 2
Member since 2024
The calmest hour of my week. I leave lighter every time.

Person 3
Member since 2024
Question 1
+
The answer to question 1, in a sentence or two.
Question 2
+
The answer to question 2, in a sentence or two.
Question 3
+
The answer to question 3, in a sentence or two.
Question 4
+
The answer to question 4, in a sentence or two.
Built in, not added
The runtime owns the behaviour, so whoever draws a Block cannot leave out the keyboard or the screen reader. Use these two and watch what the page gives a screen reader.

Picture 1
A line about this picture.

Picture 2
A line about this picture.

Picture 3
A line about this picture.
Section 1
+
What a buyer should know about section 1.
Section 2
+
What a buyer should know about section 2.
Section 3
+
What a buyer should know about section 3.
Section 4
+
What a buyer should know about section 4.
Something new: save your favourites and find them again on any device.
×
Safe to share
What a Block does comes from a fixed, reviewed set of behaviours, so anyone’s Block is safe to run. The checker below is the package’s own: it refuses a setting out of range, a missing part, or a field the format does not have, and says why.
Checking…
Want to add a Block? How to contribute one.
Made with LivingBlocks
Install
npm install livingblocksOr as a plain script, then start it on the page:
<script src="https://cdn.jsdelivr.net/npm/livingblocks@0.4/dist/livingblocks.min.js"></script>
<script>LivingBlocks.start(document.body)</script>The Block’s own element carries its settings; the elements its behaviour needs carry their part:
<section data-living='{"kind": "accordion", "v": 1, "settings": {"single": true, "start": 0}}'>
<div data-living-part="item">
<button data-living-part="header">Do you ship abroad?</button>
<div data-living-part="panel">Everywhere in Europe.</div>
</div>
<!-- …more items -->
</section>Settings for a smaller window go in data-living-at, applied in order as the page’s CSS cascades:
<section data-living='{"kind": "carousel", "v": 1, "settings": {"perView": 3}}'
data-living-at='[{"query": "(max-width: 809px)", "settings": {"perView": 1}}]'>
…
</section>The ES module has every Block, the checker and the types: draw a Block in your own style, or check one someone sent you before you keep it.
import { BLOCKS, blockById, validateBlock } from 'livingblocks';
const faq = blockById('faq'); // a Block: layers, roles, parts, settings
validateBlock(JSON.parse(submitted)); // { ok, errors: ['settings.delay: a number from 0 to 60', …] }Every field and its range is in the schema, and the parts each behaviour needs in the README.
Good to know
remember it stays away for that visitor until its words change; it does not record consent.I built LivingBlocks so the components people place in uxdeck keep working on the sites they publish, then opened it, Blocks and checker included. Use it in your code, or place the same Blocks in uxdeck without writing any.
A question, an idea, or need a hand? uxdeck is made by one designer, Soufiane. I read every message and answer by email.
Or write to contact@uxdeck.app