A small app
Start from one empty HTML file and build a counter, a dropdown, and a tag search, then see how to hand values down between scopes. By the end you will have used almost all of hyper-scope's core.
Create an empty file somewhere on your computer, with a name like i-love-hyper-scope.html. Paste in the content below and open it in a browser.
If you see "I love hyper-scope" on the screen, you are ready. Put the scripts in head. Tools that swap the page may run scripts inside body again, and there must be only one runtime.
Build a counter
This is the smallest example. It has all three basics: holding state, listening for events, and showing the state on screen.
Declare state
The element with h-scope and everything inside it form one scope. The object you write as the value becomes that scope's state. Here it is just count.
Listen for events
@click is short for h-on:click. Each click runs the expression after it, count++. Inside an expression you read and write scope values by their names.
React to changes
h-text fills the element's text with the result of the expression. When count changes, hyper-scope fills it again on its own. You never have to ask for a re-render.
Build a dropdown
Next is a dropdown that opens on click and closes when you click outside it.
Show and hide
h-show shows the element only while the expression is true. The element stays in the DOM and only its display changes. To add and remove it entirely, use <template h-if>.
Make your own attribute
h-outside is not a built-in attribute. This site registers it with a plugin. You can make the attributes you need in a few lines like this. The function you return is called when the element goes away, and cleans up.
Build a search input
This example is a little longer. It searches HTML tags by name and filters the list by what you type.
Put values in text
To put a value into text, as in {{ tag.name }}, set h-text-inline on the element that holds that text. It reads only the text directly under the element. To read the text of inner elements too, write a selector as the value. The example above uses <li h-text-inline="code">, so it reads the text of <li> and the text of the <code> inside it. Without it, {{ }} stays as plain text. Server-rendered pages contain text written by users, so it reads only in the elements you mark. Attributes such as h-text work without h-text-inline. h-text-inline →
Write it on several lines
The value of h-scope is a plain JavaScript object, so you can split it across lines. If it grows longer, move it into a <script type="scope"> directly under the scope. Whatever you return there is merged into the scope.
Bind to inputs
h-model keeps an input and a value in sync. Typing changes search, and changing search changes the input. If the server rendered a value, that becomes the initial value.
Computed values
get found() is a JavaScript getter. You read it like a value, not call it like a function. When tags or search, which it reads, change, every place that uses this value changes too.
Instead of writing a getter in the object, you can also use an attribute. h-computed:name="expression" creates a computed value that the element and its contents can read. It is read-only. Attribute names become lowercase, so write total-price and read totalPrice.
Loop
h-for renders its contents once for each item in a list. With :key, the same item reuses its element when the list changes.
Hand values down to descendants
When you put a scope inside a scope, the inner one cannot see the outer one's values. h-scope is a boundary. If the value is just one level out, take it with h-inherit="name". But the inner scope has to know the outer scope's variable name, so this breaks easily as the distance grows.
h-provide and h-inject put a label between them. The ancestor says "I provide this under this label", and the descendant says "I receive whatever is under this label". Neither needs to know the other's variable names.
Provide
h-provide:theme="theme" hands a value down under the label theme, from that element inward. The expression is evaluated where the element sits. So even if the same element has h-scope, the provide comes before that scope, and that scope can receive it right away. You can also put it on an element without h-scope. Descendants any number of levels down can receive it.
When a scope provides its own value from a script, use $provide('label', value) (in a function scope, the provide tool).
Inject
h-inject="theme" walks up the DOM, finds what the nearest ancestor provided, and reads it under that name. Separate several with spaces: h-inject="theme cart". An injected name is read-only, so you cannot assign another value to it (doing so logs a console error). You can change the inside of the received object (theme.color). If the value itself must be replaced, ask the providing side with $emit.
To receive under a different name, or to set a value for when nobody provides one, use $inject in an expression: h-scope="{ look: $inject('theme', { color: 'gray' }) }".
Changes flow both ways
What goes down is the same object, not a copy. When the ancestor changes theme.color, the receiver's screen changes. When a descendant changes the received theme.color, the ancestor's side changes. So do not provide a number or a string directly. Wrap it in an object. A bare value only passes over once, as it was at that moment.
When to use it
- Inside components. A component's template cannot see the outer scope. For a deeply nested component to get something from outside, pass it through props at every level or receive it at once with
inject. - In fragments inserted later. Fragments fetched from the server and inserted later also receive what the ancestors of their insertion point provide. A server partial only needs to know "I receive
cart". It does not need to know which page it will be inserted into. - When the same fragment is used in many places. Each page can use a different variable name in the ancestor, as long as the label is the same.
For a value that every page shares, hand it down to the whole document with the plugin's provide. It survives across visits. Use h-provide when the value belongs "only inside this region". Two regions on a page can provide the same label separately, and each descendant receives the one from its own ancestor.
Recap
If you made it this far, you have used these attributes.
h-scope— scope and stateh-on(@) — eventsh-text— texth-show— show and hideh-model— inputsh-for— loops
The rest is in Attributes, Properties, and Functions. Form validation and Turbo are covered in Extensions.