DEV Community

Cover image for ⭐ State.js + Keys.js Tutorial - Build Interactive UI with Pure HTML & CSS
iDev-Games
iDev-Games

Posted on

⭐ State.js + Keys.js Tutorial - Build Interactive UI with Pure HTML & CSS

State.js gives you reactive state.

Keys.js gives you reactive keyboard input.

Together, they let you build interactive UI, controls, and even simple games using only HTML attributes, with no JavaScript logic.

This tutorial teaches:

  • how Keys.js tracks keyboard state
  • how State.js reacts to it
  • how to bind keys to state
  • how to build interactive UI
  • how to animate with CSS
  • how to build a small “move the box” demo

Let’s go.


⭐ 1. What Keys.js Actually Does

Keys.js listens to keyboard events and exposes them as:

  • CSS variables
  • HTML attributes
  • classes

Example:

<div data-keys></div>
Enter fullscreen mode Exit fullscreen mode

Keys.js automatically creates:

--keys-a: 1 or 0
--keys-space: 1 or 0
--keys-last: "a"
Enter fullscreen mode Exit fullscreen mode

And adds classes like:

.keys-a
.keys-space
Enter fullscreen mode Exit fullscreen mode

Learn more: Keys.js basics


⭐ 2. What State.js Does With That

State.js can react to:

  • CSS variables
  • attributes
  • events
  • conditions

So when Keys.js updates:

--keys-left: 1
Enter fullscreen mode Exit fullscreen mode

State.js can use that to:

  • update state
  • change classes
  • animate with CSS
  • move elements

This is the reactive input pipeline:

Keyboard → Keys.js → CSS variables → State.js → UI


⭐ 3. Basic Setup

Include both libraries:

<script src="https://cdn.jsdelivr.net/npm/@idevgames/state-js/src/state.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@idevgames/keys-js/src/keys.min.js"></script>
Enter fullscreen mode Exit fullscreen mode

Add the Keys.js root:

<div data-keys></div>
Enter fullscreen mode Exit fullscreen mode

Now the entire page reacts to keyboard input.


⭐ 4. Detecting Key Presses with CSS

Let’s show when the user presses the spacebar.

<div class="indicator"
     data-keys
     data-state
     data-state-class="active: keys.space">
  Press SPACE
</div>
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • data-keys → enable Keys.js
  • keys.space → true when space is pressed
  • data-state-class="active: keys.space" → add .active when pressed

CSS:

.indicator {
  padding: 20px;
  background: #444;
  color: white;
  transition: background 0.2s;
}

.indicator.active {
  background: #4caf50;
}
Enter fullscreen mode Exit fullscreen mode

This is a reactive UI element driven by keyboard input.


⭐ 5. Using Keys to Change State

Let’s increment a counter when the user presses ArrowUp.

<div id="counter"
     data-state
     data-count="0"
     data-state-on="keys.arrowup => count += 1"
     data-state-text="Count: {count}">
</div>
Enter fullscreen mode Exit fullscreen mode

What happens:

  • Keys.js fires a keys.arrowup event
  • State.js runs count += 1
  • UI updates automatically

This is the State.js event system in action.

Learn more: State.js event system


⭐ 6. Using Keys to Move an Element (No JS Logic)

Let’s build a simple “move the box” demo.

HTML

<div id="player"
     data-state
     data-x="100"
     data-y="100"
     data-state-on="
       keys.arrowleft => x -= 5;
       keys.arrowright => x += 5;
       keys.arrowup => y -= 5;
       keys.arrowdown => y += 5
     ">
</div>
Enter fullscreen mode Exit fullscreen mode

CSS

#player {
  width: 50px;
  height: 50px;
  background: dodgerblue;
  position: fixed;
  transform: translate(var(--state-x), var(--state-y));
}
Enter fullscreen mode Exit fullscreen mode

What’s happening:

  • Keys.js detects arrow keys
  • State.js updates data-x and data-y
  • CSS moves the element using transforms
  • No JavaScript logic
  • No game loop
  • No re-renders

This is DOM-native movement.


⭐ 7. Smooth Movement (Holding Keys)

Keys.js exposes “held down” state as CSS variables:

--keys-arrowleft: 1
Enter fullscreen mode Exit fullscreen mode

We can use autofire to move continuously:

<div id="player"
     data-state
     data-x="100"
     data-y="100"
     data-state-autofire="
       if keys.arrowleft then x -= 3 every 16ms;
       if keys.arrowright then x += 3 every 16ms;
       if keys.arrowup then y -= 3 every 16ms;
       if keys.arrowdown then y += 3 every 16ms
     ">
</div>
Enter fullscreen mode Exit fullscreen mode

This creates smooth WASD/arrow movement without JS.

Learn more: State.js intervals


⭐ 8. Using Keys to Trigger Animations

<div id="box"
     data-state
     data-state-class="jump: keys.space">
</div>
Enter fullscreen mode Exit fullscreen mode

CSS:

#box.jump {
  animation: jump 0.3s ease;
}
Enter fullscreen mode Exit fullscreen mode

Pressing space triggers a CSS animation.


⭐ 9. Using Keys to Toggle UI

<div id="menu"
     data-state
     data-open="false"
     data-state-on="keys.escape => open = !open"
     data-state-class="visible: open">
  Menu Content
</div>
Enter fullscreen mode Exit fullscreen mode

Press ESC → open/close menu.


⭐ 10. Full Example — Move a Box + Change Color + Show Key

<div data-keys></div>

<div id="player"
     data-state
     data-x="100"
     data-y="100"
     data-color="blue"

     data-state-on="
       keys.arrowleft => x -= 5;
       keys.arrowright => x += 5;
       keys.arrowup => y -= 5;
       keys.arrowdown => y += 5;
       keys.space => color = 'red'
     "

     data-state-style="background: {color}">
</div>

<div id="info"
     data-state
     data-state-text="Last key: {keys.last}">
</div>
Enter fullscreen mode Exit fullscreen mode

CSS:

#player {
  width: 50px;
  height: 50px;
  position: fixed;
  transform: translate(var(--state-x), var(--state-y));
}
Enter fullscreen mode Exit fullscreen mode

This gives you:

  • movement
  • color change
  • last key display
  • all reactive
  • all declarative
  • zero JS logic

⭐ Why State.js + Keys.js Works So Well

Because they follow the same philosophy:

✔ HTML = structure

✔ CSS = behavior

✔ Attributes = API

✔ JS = engine, not authoring

✔ Zero build step

✔ Zero dependencies

✔ Browser-native

Top comments (0)