Bind any input to a variable. Change one, the other updates instantly.
Lives-bind
Hello, {demoName}
color: {demoColor} · size: {demoSize}px
View code
<!-- HTML --><inputtype="text"s-bind="demoName"><inputtype="color"s-bind="demoColor"><inputtype="range"s-bind="demoSize"min="20"max="200"><!-- Output: binds the same variable, with dynamic styling --><spans-bind="demoName"s-css="color: {demoColor}; font-size: {demoSize + 'px'}">
Hello, {demoName}
</span><!-- Store --><script>
let store = {
data: {
demoName: { type: String, default: 'SensibleJS' },
demoColor: { type: String, default: '#6366f1' },
demoSize: { type: Number, default: 32 }
}
};
</script>
Conditional Rendering
s-if
Show or hide elements based on any expression. No class toggling needed.
Lives-if
Welcome back! This element is conditionally visible.
These details only appear when the checkbox is checked. Toggle it to see the element appear and disappear from the DOM flow.
You can also use negation: this shows when greeting is hidden.
View code
<!-- Checkbox controls a Boolean variable --><inputtype="checkbox"s-bind="showGreeting"> Show greeting
<!-- Show when true --><divs-if="showGreeting">Welcome back!</div><!-- Show when false (negation) --><divs-if="!showGreeting">Greeting is hidden</div><!-- Works with any JS expression --><buttons-if="items.length > 0">Checkout</button><ps-if="name != '' && name.length > 3">Hello!</p><!-- Store --><script>
let store = {
data: {
showGreeting: { type: Boolean, default: true },
showDetails: { type: Boolean, default: false }
}
};
</script>
Animation
s-transition
Animate elements when they enter or leave. Add s-transition="name" to any element with s-if. SensibleJS applies CSS classes at each stage — you define the transitions in CSS.
Toggle CSS classes and HTML attributes based on expressions. Add s-debounce="ms" to text inputs to delay updates until the user stops typing — useful for validation, search, or API calls where you don't want to react on every keystroke.
Lives-class · s-attr · s-debounce
Both inputs use s-debounce="300" — the validation waits 300ms after you stop typing before updating. Try typing quickly vs. slowly to see the difference.
Form is validFill in both fields correctly
View code
<!-- s-debounce: delay binding updates (in milliseconds) --><!-- Without it, the variable updates on every keystroke. --><!-- With it, updates wait until the user pauses typing. --><inputs-bind="username"s-debounce="300"><!-- s-class: toggle CSS classes based on expressions --><divs-class="valid-border: formOk; invalid-border: !formOk">
Form status
</div><!-- s-attr: set/remove HTML attributes dynamically --><!-- false/null/undefined removes the attribute --><buttons-attr="disabled: !formOk">Submit</button><!-- Computed value: derived from other variables --><script>
let store = {
data: {
username: { type: String, default: '' },
email: { type: String, default: '' },
formOk: { computed: "username.length >= 3 && email.indexOf('@') >= 0" }
}
};
</script>
General Events
s-on
Bind any DOM event with modifiers. Supports .prevent, .stop, .enter, and .escape.
Lives-on
{onDemoLog}
View code
<!-- Bind keyboard events with modifiers --><inputs-bind="message"s-on="keydown.enter: addLog()"><!-- Multiple events on one element --><divs-on="mouseover: hovered = true; mouseout: hovered = false"><!-- Prevent default (e.g. form submit) --><forms-on="submit.prevent: handleSubmit()"><!-- Modifiers: .prevent, .stop, .enter, .escape -->
Element References
s-ref
Name elements with s-ref and access them in expressions via $refs.name. Useful for focusing inputs, reading dimensions, or calling DOM methods.
Lives-ref
View code
<!-- Name an element with s-ref --><inputtype="text"s-ref="myInput"><!-- Access it via $refs in any expression --><buttons-click="$refs.myInput.focus()">Focus</button><buttons-click="$refs.myInput.value = ''">Clear</button><!-- Works in any directive expression --><ps-if="$refs.panel.scrollHeight > 200">Content is scrollable</p>
Content Binding
s-text & s-html
Set element content directly from an expression. s-text sets safe text content, s-html sets raw HTML. Simpler than s-bind for display-only elements.
Lives-text · s-html
View code
<!-- s-text: sets textContent (HTML tags are escaped) --><ps-text="username"></p><!-- s-html: sets innerHTML (renders HTML) --><divs-html="richContent"></div><!-- Expressions work too --><spans-text="'Hello, ' + name"></span><divs-html="'<strong>' + title + '</strong>'"></div><!-- Use s-text for user input (XSS safe)
Use s-html only for trusted content -->
Image Binding
s-bind & s-src
Bind image sources to variables. Use s-src inside loops for dynamic galleries.
Lives-bind · s-src
{photos.length} photo(s) ·
{avatarUrl}
View code
<!-- s-bind on <img> sets the src attribute --><inputtype="text"s-bind="avatarUrl"><imgs-bind="avatarUrl"><!-- s-src for dynamic sources inside loops --><divs-for="photo of photos"s-key="photo.id"><imgs-src="{photo.url}"><buttononclick="removePhoto(this)">×</button></div><!-- Store --><script>
let store = {
data: {
avatarUrl: {
type: String,
default: 'https://picsum.photos/id/64/200'
},
photos: {
type: Array,
default: [
{ id: 1, url: 'https://picsum.photos/id/10/200' },
{ id: 2, url: 'https://picsum.photos/id/20/200' }
],
persist: false
}
}
};
// Remove by key — same pattern as any s-for list
function removePhoto(btn) {
let key = btn.closest('[s-key-value]').getAttribute('s-key-value');
for (let i = 0; i < photos.length; i++) {
if (String(photos[i].id) === key) { photos.splice(i, 1); return; }
}
}
</script>
Event Handling
s-click
Execute expressions on click. Simple counters, toggles, or function calls.
Lives-click
{clickCount} clicks
You're on fire! The color changed because clickCount > 10.
View code
<!-- Inline expressions — no functions needed --><buttons-click="clickCount++">Click me</button><buttons-click="clickCount = 0">Reset</button><!-- Combine with s-bind and s-css for reactive feedback --><spans-bind="clickCount"s-css="color: {clickCount > 10 ? 'green' : 'white'}">
{clickCount} clicks
</span><!-- Push to arrays, toggle booleans, call functions --><buttons-click="items.push({id: Date.now(), text: 'New'})">Add Item</button><buttons-click="menuOpen = !menuOpen">Toggle Menu</button><!-- Store --><script>
let store = {
data: {
clickCount: { type: Number, default: 0 }
}
};
</script>
Built-in Feature
localStorage Persistence
Variables survive page refreshes automatically. No extra code needed.
Livepersist: true
{persistDemo}Type above, then reload the page. Your text will still be here.
View code
<!-- Just bind an input — persistence is automatic --><inputtype="text"s-bind="persistDemo"><!-- Store: persist is true by default --><script>
let store = {
persist: true, // Save all variables to localStorage
localPrefix: '__', // Key prefix to avoid collisions
data: {
persistDemo: { type: String, default: '' },
// Override per variable:
userPrefs: { type: Object, default: {}, persist: true }, // always saved
tempInput: { type: String, default: '', persist: false } // never saved
}
};
</script>
Store Feature
Watchers
Add a watch function to any store variable. It receives (newValue, oldValue) on every change — useful for side effects, validation, or derived updates.
Livewatch
{watchLog}
View code
<!-- Watchers are defined in the store --><script>
let store = {
data: {
username: {
type: String,
default: '',
watch: function(newVal, oldVal) {
console.log('Changed from', oldVal, 'to', newVal);
}
}
}
};
</script><!-- watch receives (newValue, oldValue) — useful for
comparisons, validation, or triggering side effects -->
Store Feature
onInit Lifecycle Hook
Run setup code after SensibleJS finishes initializing. The onInit function receives the data object, so you can set initial values, fetch data, or trigger side effects.
LiveonInit
{initMessage}
This message was set by onInit when the page loaded.
View code
<script>
let store = {
data: {
message: { type: String, default: '' }
},
onInit: function(data) {
// Runs after all data is bound and DOM is ready
data.message = 'Initialized at ' + new Date().toLocaleTimeString();
// Common uses:// - Set values based on URL params// - Fetch initial data from an API// - Run one-time setup logic
}
};
</script>
Directive
s-cloak — Hide Until Ready
Prevents the flash of raw template expressions (like {name}) before SensibleJS initializes. Elements with s-cloak stay hidden until all data is bound, then the attribute is automatically removed.
Lives-cloak
The greeting below uses s-cloak — it was hidden until SensibleJS finished loading, so you never saw the raw {cardName} template.
Welcome, {cardName}!Enter a name in the mini-app below to see s-cloak in action.
View code
<!-- Without s-cloak: user briefly sees "{name}" --><ps-bind="name">Hello, {name}!</p><!-- With s-cloak: element stays hidden until data is ready --><ps-cloaks-bind="name">Hello, {name}!</p><!-- No CSS needed — SensibleJS injects the rule automatically:
[s-cloak] { display: none !important }
The attribute is removed after initialization. -->
Click Away
s-unclick
Execute an expression when a click occurs outside the element. Perfect for closing dropdowns, modals, and popovers.
Lives-unclick
Edit profile
Settings
Sign out
Click the button, then click anywhere outside the dropdown to close it.
View code
<!-- Wrap button + dropdown so clicks inside don't close it --><divs-unclick="menuOpen = false"><buttons-click="menuOpen = !menuOpen">Menu</button><!-- Dropdown closes when you click outside the wrapper --><divs-if="menuOpen"><div>Edit</div><div>Settings</div><div>Sign out</div></div></div>
Inline Variables
s-data
Define variables directly in HTML without a store. Useful for quick prototypes or self-contained widgets.
Lives-data
Counter: {inlineCount}
No <script> store needed — variables are defined right in the HTML with s-data.
View code
<!-- Define variables directly in HTML --><divs-data="{count: 0, message: 'Hello!'}"><spans-text="message"></span><spans-bind="count">{count}</span><buttons-click="count++">Increment</button></div><!-- No store needed — great for quick prototypes -->
Putting It Together
A Complete Mini-App
All directives working together in a profile card builder.
Liveall directives
{skill.name}
{cardEmail}
{skill.name}
{cardSkillCount} skill(s)
Fill in the fields to see your card.
View code
<!-- This mini-app uses every SensibleJS directive --><!-- s-bind, s-debounce, s-class: validated name input --><inputs-bind="cardName"s-debounce="200"s-class="valid: cardName.length >= 2"s-ref="nameInput"><!-- s-blur: avatar updates on blur, not keyup --><inputs-bind="cardAvatar"s-blur><!-- s-if + s-transition: animated email reveal --><divs-if="showEmail"s-transition="slide">...</div><!-- s-on: add skill on Enter key --><inputs-on="keydown.enter: addSkill()"><!-- s-for + s-key: render skill tags --><spans-for="skill of skills"s-key="skill.id">{skill.name}</span><!-- s-click: add skill --><buttons-click="addSkill()">Add</button><!-- s-ref: focus name input from button --><buttons-click="$refs.nameInput.focus()">Focus</button><!-- s-attr: disable button when form is default --><buttons-attr="disabled: !changed">Reset</button><!-- Card preview: s-css, s-text, s-html, s-src --><divs-css="border-color: {accent}"><imgs-src="{avatar}"><divs-text="name"></div><!-- safe text --><divs-html="bio"></div><!-- renders HTML --><spans-bind="skillCount">{skillCount} skill(s)</span><!-- computed --></div>