ygdrassil
v2026.1.6
Published
**Ygdrassil** is a lightweight state machine library that syncs state with the URL hash/query for bookmarkable application states.
Readme
Ygdrassil
Ygdrassil is a lightweight state machine library that syncs state with the URL hash/query for bookmarkable application states.
Available in two flavors:
- React: Declarative JSX components with hooks
- Vanilla JS: Zero dependencies, works anywhere (includes Web Components)
TLDR - Quick Links
- 📚 Common Features - What Ygdrassil does
- ⚛️ React Version - Installation, API, and usage for React
- 🎯 Vanilla JS Version - Pure JavaScript with no dependencies
- 🚀 Demo / Examples - See it in action
Features
Common to both React and Vanilla versions:
- 🔗 URL-Based State: State synced with URL hash for bookmarkable/shareable app states
- 🎯 Transition Control: Define allowed transitions between states
- 🪝 Lifecycle Hooks:
onEnterandonExitcallbacks for state changes - 🔍 Query Parameters: Built-in query parameter management
- 🔄 Multiple Machines: Run multiple independent state machines simultaneously
- 📦 Small & Fast: Minimal footprint
React Version
Installation
npm install ygdrassilimport { StateMachine, State, useStateMachine } from 'ygdrassil'React Components & API
<StateMachine> Provider
name- Identifies the machine; appears in URL asyg-<name>initial- Initial state to render (optional)className- Wraps children in a<div>with these classes (optional)
<State> Component
onEnter- Function called when entering the state (optional)onExit- Function called when exiting the state (optional)transition- Array of allowed next states (optional, defaults to any state)
useStateMachine() Hook
Returns an object with:
currentState- String of the current state's namequery- Object mirroring URL query-string valuesgotoState(name)- Navigate to a different stateclose()- Unload the state machine and URL paramsis(name)- Check if current state matches given nameavailableTransitions- Array of allowed transitions from current statesetQuery(obj, replace?)- Update query stringobj- Key-value pairs to setreplace- If true, replaces entire query string
registerState(name, transition, onEnter, onExit)- Dynamically register a stateunregisterState(name)- Remove a stateparam- URL parameter name (e.g.,yg-demo)
Navigation Components
<StateButton to="stateName">- Button for state navigation- Auto-adds
activeclass whentomatches current state - Optional
onClickruns before state change - Optional
className
- Auto-adds
<StateLink to="stateName">- Same asStateButtonbut renders an<a>tag<ExternalButton machine="machineName" to="stateName">- Navigate from outside a StateMachine context<ExternalLink>- Same asExternalButtonbut renders an<a>tag
React Example
import { StateMachine, State, useStateMachine } from 'ygdrassil'
function App() {
return (
<StateMachine name="app" initial="home">
<Navigation />
<State name="home">
<h1>Home Page</h1>
</State>
<State name="about" onEnter={() => console.log('About loaded')}>
<h1>About Page</h1>
</State>
</StateMachine>
)
}
function Navigation() {
const { StateButton } = useStateMachine()
return (
<nav>
<StateButton to="home">Home</StateButton>
<StateButton to="about">About</StateButton>
</nav>
)
}Vanilla JavaScript Version
The vanilla version provides zero-dependency state machine functionality for any web project. No React, no build tools, no frameworks required.
Installation
Via npm:
npm install ygdrassilProgrammatic API:
import { StateMachine } from 'ygdrassil/vanilla'Web Components (Custom HTML Elements):
import 'ygdrassil/vanilla/elements'Or use directly in browser:
<script type="module">
import { StateMachine } from './node_modules/ygdrassil/vanilla/StateMachine.js'
// or for Web Components:
import './node_modules/ygdrassil/vanilla/StateMachine.elements.js'
</script>Vanilla Programmatic API
import { StateMachine } from 'ygdrassil/vanilla'
const machine = new StateMachine({
name: 'app',
initial: 'home',
onEnter: (state) => console.log(`Entering: ${state}`), // Global hook
onExit: (state) => console.log(`Exiting: ${state}`), // Global hook
states: {
home: {
onEnter: () => {
document.getElementById('app').innerHTML = '<h1>Home</h1>'
},
transition: ['about'] // Can only go to 'about' from here
},
about: {
onEnter: () => {
document.getElementById('app').innerHTML = '<h1>About</h1>'
}
}
}
})
// Navigate to a state
machine.gotoState('about')
// With query parameters
machine.gotoState('profile', { userId: 123 })
// Subscribe to state changes
const unsubscribe = machine.subscribe(({ currentState, query }) => {
console.log('State changed:', currentState)
})Methods:
gotoState(name, data?, replace?)- Navigate to a stateregisterState(name, definition)- Add a state dynamicallyunregisterState(name)- Remove a stateclose()- Deactivate and clean upis(name)- Check if current state matches namegetAvailableTransitions()- Get allowed next statesgetQuery()- Get query parameters as objectsetQuery(obj, replace?)- Update query parameterssubscribe(listener)- Listen for state changesdestroy()- Clean up and remove listeners
Properties:
currentState- Current active state namestates- Registry of all statesname- Machine nameparam- URL parameter name (yg-<name>)
Vanilla Web Components
Use declarative HTML custom elements:
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<state-machine name="app" initial="home">
<!-- Navigation -->
<state-nav to="home">Home</state-nav>
<state-nav to="about">About</state-nav>
<state-nav to="contact" data-section="general">Contact</state-nav>
<!-- States -->
<state-def name="home">
<h1>Welcome Home!</h1>
<p>This is the home page.</p>
</state-def>
<state-def name="about" transition="home,contact">
<h1>About Us</h1>
<p>Can navigate to home or contact from here.</p>
</state-def>
<state-def name="contact">
<h1>Contact</h1>
<state-query format="list"></state-query>
</state-def>
</state-machine>
<script type="module" src="./node_modules/ygdrassil/vanilla/StateMachine.elements.js"></script>
</body>
</html>Custom Elements:
<state-machine name="app" initial="home">- Container for states<state-def name="stateName" transition="state1,state2">- Define a state<state-nav to="stateName" type="button|link">- Navigation button/link<state-query format="json|list">- Display query parameters
Events:
state-enter- Fired when entering a statestate-exit- Fired when exiting a statestate-change- Fired on any state change
Vanilla Features
- ✅ Zero dependencies - pure JavaScript
- ✅ Global
onEnterandonExithooks (runs for all state changes) - ✅ State-level lifecycle hooks
- ✅ Transition control
- ✅ Query parameter management
- ✅ Event subscription for reactive updates
- ✅ Web Components for declarative HTML usage
- ✅ Helper functions:
createStateButton(),createStateLink() - ✅ Works in all modern browsers (ES6+)
📖 Full Documentation: See vanilla/README.md for complete API reference and advanced examples.
Demo / Example / Test
Online Demo
View the live demo: https://fingerskier.github.io/ygdrassil/
Run Locally
React demo:
git clone https://github.com/fingerskier/ygdrassil.git
cd ygdrassil
npm install
npm run devVanilla JS demos:
cd vanilla
# Open index.html (programmatic API) or elements-demo.html (Web Components) in your browser
# Or serve with:
npx serve .Contributing
Contributions welcome! Please submit issues and pull requests to the repository.
License
MIT License - see the main repository for details.
