Designing Freedeck's UI/UX
October 16, 2025This article is still being written! This article will explain my thought process behind Freedeck...
Read PostThis article is still being written! This article will explain my thought process behind Freedeck...
Read PostWhy and how I created a library for vanilla JS for use with the CSS grid. ## I: Freedeck / The Pr...
Read PostWhy and how I created a library for vanilla JS for use with the CSS grid.
Freedeck is my open source alternative to Elgato's Stream Deck.
It's a webapp macro-pad where buttons are aligned in a CSS grid.
First, we need to see the CSS grid itself: an adjustable row-column display.
Objects usually aren't swappable super easily, or atleast in a way where you can:
This itself isn't hard to code on its own, but fits better in a generic library.
gridItemDrag is my proposed solution, which takes an HTML element (that uses a css grid) and has items in it.
While dragging, it can set the highlighted position to where an item will be dragged onto, and can disallow you from setting (or picking up) an item.
This code is a heavily modified excerpt of Freedeck's implementation. Most complexities/oddities of how Freedeck handles Tiles have been removed for simplicity sake.
import gridItemDrag from "./lib/gridItemDrag.js";
gridItemDrag.setFilter("#keys .button"); // These are the draggable items (CSS selector)
gridItemDrag.unmovableClass = ".builtin"; // These are not movable (CSS selector)
gridItemDrag.setContext(document.querySelector("#keys")); // This is the grid.
gridItemDrag.on("drop", (event, originalIndex, targetIndex) => {
// When an item is dropped onto another.
// originalIndex is the dragged item, targIndex is the one that originalIndex was dropped on.
// gID works on indexes, not individual elements so we must query them.
const changed = document.querySelector(`#keys .button.k-${originalIndex}`);
// Grab the button we dragged and swap it!
changed.classList.remove(`k-${originalIndex}`);
changed.classList.add(`k-${targetIndex}`);
// event.target is a direct reference to the dropped element.
event.target.classList.remove(`k-${targetIndex}`);
event.target.classList.add(`k-${originalIndex}`);
// targetInter is a Freedeck-specific attribute that fully details a tile.
const targetInter = JSON.parse(changed.getAttribute("data-interaction"));
universal.send(universal.events.companion.move_tile, { // This will send an event up to the server teling it to swap the tiles.
name: changed.getAttribute("data-name"),
item: changed.getAttribute("data-interaction"),
newIndex: targetIndex,
oldIndex: originalIndex,
});
// Although it does get saved on the server side, the client is expected to do this refresh to stay up-to-date with the configuration.
changed.pos = targetIndex;
changed.setAttribute("data-interaction", JSON.stringify(targetInter));
});
The library is open to grab from Freedeck's GitHub repo. It will eventually be placed in its own repository.
gridItemDrag is compatible with any library, as long as the runtime supports document.query, etc..
gridItemDrag can be found in the Freedeck GitHub repository (click here)
An implementation example can be found in Freedeck's code (click here)
Go Back