k-context is a non-rendering state container inspired by React's useContext. Place it in the DOM as an ancestor of components that need shared state. Descendant components locate the nearest k-context via closest('k-context'), read and write data with its methods, and react to changes by listening for its events.
Declare a <k-context> element in your markup. It is invisible and renders nothing; its display: contents style ensures its children remain in the normal document flow.
<k-context id="myContext"></k-context>
You can pre-populate the store declaratively by setting the data attribute to a valid JSON string:
<k-context id="myContext" data='{"count":0,"theme":"dark"}'></k-context>
Use set() and get() to write and read data by key:
const ctx = document.querySelector('k-context');
ctx.set('count', 0);
ctx.set('theme', 'dark');
context:create fires when a new key is set for the first time. context:set fires when an existing key's value changes. context:delete fires when a key is removed. All events bubble and are composed.
const ctx = document.querySelector('k-context');
ctx.addEventListener('context:create', e => {
console.log('Created:', e.detail.key, e.detail.value);
});
ctx.addEventListener('context:set', e => {
console.log('Updated:', e.detail.key, 'from', e.detail.oldValue, 'to', e.detail.value);
});
ctx.addEventListener('context:delete', e => {
console.log('Deleted:', e.detail.key, 'was', e.detail.value);
});
Child components locate the nearest ancestor context using closest('k-context'). This follows the same scoping pattern used by all Kempo UI parent–child relationships.
// Inside a child component, find the nearest ancestor context:
const ctx = this.closest('k-context');
const count = ctx?.get('count');
To react to context changes from inside a shadow-DOM component, listen on the context element directly — events bubble and are composed: true so they also cross shadow boundaries.
Set a persistent-id to automatically save the store to localStorage and restore it on the next page load — the same convention used by Split, Accordion, Tabs and Aside. The data is reloaded in connectedCallback (before descendants read it) and re-saved on every set(), delete() and clear().
<k-context persistent-id="app-settings"></k-context>
Because the entire store is JSON-serialized into a single value, keep persisted contexts to small, serializable state (settings, selections, ids) — not large or non-JSON data. Scope what you persist by giving config its own persistent-id context and leaving transient state in a separate, non-persistent k-context.
<k-context data='{"count":0}'>
<button
onclick="const ctx=this.closest('k-context'); ctx.set('count', (ctx.get('count') || 0) - 1)"
>-</button>
<count-view></count-view>
<button
onclick="const ctx=this.closest('k-context'); ctx.set('count', (ctx.get('count') || 0) + 1)"
>+</button>
<button
onclick="this.closest('k-context').clear()"
>Clear</button>
</k-context>
<script type="module">
import '../src/components/Context.js';
import ShadowComponent from '../src/components/ShadowComponent.js';
import { html } from '../src/lit-all.min.js';
class CountView extends ShadowComponent {
connectedCallback() {
super.connectedCallback();
const ctx = this.closest('k-context');
ctx?.addEventListener('context:create', () => this.requestUpdate());
ctx?.addEventListener('context:set', () => this.requestUpdate());
ctx?.addEventListener('context:delete', () => this.requestUpdate());
}
render() {
const count = this.closest('k-context')?.get('count');
return count !== undefined ? html`${count}` : html`<code>undefined</code>`;
}
}
customElements.define('count-view', CountView);
</script>
k-context extends LightComponent. It renders nothing and sets display: contents on itself so its children remain in the normal document flow. It accepts an optional data attribute containing a JSON string to pre-populate the context state.
| Attribute | Type | Description |
|---|---|---|
data |
String (JSON) |
A JSON string representing the initial store state. Reflects back to the attribute as state changes via the component's methods. Defaults to '{}'. |
persistent-id |
String |
When set, the store is auto-saved to localStorage (key context-persistent-id-<id>) on every change and restored on connect. Defaults to null (no persistence). |
| Method | Description |
|---|---|
set(key, value) |
Creates or updates a key. Fires context:create if the key is new, context:set if it already existed. |
get(key) |
Returns the stored value for key, or undefined if not set. |
has(key) |
Returns true if key exists in the store. |
delete(key) |
Removes a key and fires context:delete. No-op if the key does not exist. |
clear() |
Calls delete() on every key, firing context:delete for each. |
getData() |
Returns a shallow copy of the entire data store as a plain object. |
All events bubble and are composed: true, so they cross shadow DOM boundaries.
| Event | Fired when | detail shape |
|---|---|---|
context:create |
A new key is set for the first time | { key, value } |
context:set |
An existing key's value is changed | { key, value, oldValue } |
context:delete |
A key is removed via delete() or clear() |
{ key, value } |