In an early version of my framework, a template could say this:
<p b-text="count"></p> <button b-on-click="increment">+1</button> Short, readable, and no ceremony. In Brio 4.0 I changed it to this:
<p b-text="$data.count"></p> <button b-on-click="$data.count++">+1</button> Longer, and more to type. I did it on purpose, and I'm not sure everyone would agree, so here's the reasoning.
What a bare name had to guess
When you write count in a template, something has to decide where count lives. In my framework, before 4.0, the docs described the order like this: expressions saw the nearest b-data scope, then component setup values, then app state. And missing names could fall through to JavaScript globals.
That's a reasonable rule. It's also a rule you have to carry in your head, and it gets harder as the page grows. Three things bothered me.
1. The same name can mean different things in different places. Imagine (this is illustrative) a page where the app state has name, a nested b-data also has name, and a loop item has name. Reading b-text="name", you can't tell which one you're looking at without knowing the lookup order and the nesting. Moving the element can change what it shows.
2. A typo can quietly work. Falling through to globals sounds harmless until you remember what browsers put on window: name, status, parent, top, length, and more. Imagine you forget to define status in your state. A bare status doesn't fail. It shows a global, and you get a wrong value instead of an error.
3. Reading code means reading the framework. Even in v3 my own docs said you could write the context explicitly "to avoid confusion when there are multiple functions with the same name in different contexts". I had a feature whose docs recommended not using it in the confusing cases. That's a hint.
What I changed
Starting in 4.0, expressions run in strict mode and nothing is resolved implicitly. You name the scope:
-
$datafor the nearestb-dataor component state -
$propsfor what a component received -
$forfor the current loop item (and$for.$parentfor the loop outside it) -
$store,$el,$event, and so on for the rest
If you forget, you don't get a mystery value. You get an error that tells you what to write:
count is not defined. Use $data.count. item is not defined. Loop variables are read through $for: use $for.item. Walking up is explicit as well: $data.$parent and $for.$parent. Nothing climbs the scope chain behind your back.
What it costs
I won't pretend this is free.
- More typing.
$data.countis longer thancount, in every binding, forever. - More noise for tiny pages. For a toggle and one counter, the prefix is mostly ceremony.
- It's a break. Anyone who used bare names had to change templates when they upgraded to 4.0.
- Vue and Alpine do bare names, and it's pleasant. I'm not claiming they're wrong. Their lookup rules work for a lot of people. I just wanted the rule to be one sentence: "the name tells you where to look."
What I gained
I can open a template I wrote months ago, read a binding, and know where its value lives without opening the JavaScript. Search for $props.title and you find every use. A typo throws instead of showing the wrong thing. And I never have to remember a lookup order, because there isn't one.
Whether that's worth the extra characters probably depends on how big your pages get and how many scopes you nest. I built this for myself, and I decided yes. I don't know if that holds for you.
Your turn
Would you trade brevity for explicitness? Do you prefer bare names and a lookup rule, or a prefix on every value? And if you've been burned by an implicit scope somewhere, I'd like to hear what happened.
The framework is Brio.js if you want to see the syntax in context.
Drafted with help from an AI assistant.

