When you write This is attribute-level reactivity -- FLIN's approach to making UI updates as cheap as possible. Session 253 refined this system to its final form, building on the reactivity engine (covered in article 028) to track dependencies not just at the component level, but at the individual attribute level. Most frameworks operate at the component level. When a state variable changes, the entire component re-renders, producing a new virtual DOM tree. The framework then diffs the old and new trees to find what changed and patches the real DOM accordingly. This works, but it does unnecessary work. Consider a component with 50 elements: When FLIN skips the comparison. It knows, at compile time, exactly which attributes depend on which variables. When During compilation, the emitter analyzes each attribute expression and records which variables it references: The compiler produces a dependency map: At runtime, each entry in this map becomes a "reactive binding." When pub struct ReactiveScope {
bindings: Vec impl ReactiveScope {
pub fn notify_change(&mut self, var_id: VariableId, vm: &mut Vm) {
if let Some(binding_ids) = self.dependency_graph.get(&var_id) {
for &binding_id in binding_ids {
let binding = &self.bindings[binding_id];
let new_value = vm.eval_expr(&binding.expression)?;
dom_set_attribute(binding.element_id, &binding.attribute, &new_value);
}
}
}
}
``` The The same tracking applies to text content: The text node For components with many text nodes (a data table with 100 rows and 5 columns = 500 text nodes), this granularity is significant. Updating one cell's value touches one text node, not 500. Form inputs need two-way binding: the input displays a variable's value, and typing in the input updates the variable. FLIN handles this with a single When the user types, FLIN's event system captures the input event and updates This is simpler than React's controlled component pattern (which requires both The compiler detects that Conditional blocks ( {if show_details}
When The List rendering ( When This is keyed diffing, similar to React's Multiple state changes in the same event handler are batched into a single DOM update: Instead of four separate DOM updates (one per state change), FLIN batches them. All four variables change, then all affected reactive bindings are re-evaluated in a single pass. The DOM is touched once, not four times. Batching happens automatically for synchronous code. All state changes within a single function call, event handler, or lifecycle hook are batched. The DOM update happens after the function returns. Values derived from other reactive values are automatically reactive: The reactivity system tracks the dependency chain: Attribute-level reactivity has specific performance characteristics: Update cost: O(k) where k is the number of bindings that depend on the changed variable. Not O(n) where n is the total number of elements. Memory cost: O(b) where b is the total number of reactive bindings. Each binding is approximately 64 bytes (element reference, attribute name, compiled expression, dependency list). Compilation cost: O(t) where t is the total number of template expressions. The compiler analyzes each expression once to determine its dependencies. For a typical page with 200 elements and 400 reactive bindings:
- Memory: ~25KB for the reactivity graph
- Update latency: <1ms for a single variable change
- Batch update: <5ms for 10 simultaneous variable changes These numbers are fast enough that the user never perceives a delay between action and visual feedback. FLIN's attribute-level granularity is closest to SolidJS's signal-based approach. Both avoid virtual DOM diffing entirely, updating the real DOM directly. The difference is in the developer experience: SolidJS requires explicit For a simple counter, the difference between component-level and attribute-level reactivity is invisible. Both produce smooth, instant updates. For a complex dashboard with 500 elements, 50 state variables, and real-time data updates, the difference is dramatic. Component-level reactivity re-renders the entire dashboard on every change, diffing 500 elements. Attribute-level reactivity updates only the specific attributes that changed -- typically 1-5 DOM operations instead of 500 element comparisons. This is the kind of performance optimization that makes the difference between a dashboard that feels sluggish and one that feels instant. And in FLIN, it happens automatically, without any developer optimization effort. --- This is Part 92 of the "How We Built FLIN" series, documenting how a CEO in Abidjan and an AI CTO built attribute-level reactivity into a component system. Series Navigation:
- [91] Slots and Content Projection
- [92] Attribute Reactivity (you are here)
- [93] Theme Toggle and Dark Mode
- [94] The Raw Tag: Escape Hatch for HTMLactive changes: the class attribute updates, and nothing else. The style attribute does not re-evaluate. The element is not destroyed and recreated. The div's children are not re-rendered. Only the specific attribute that depends on the changed variable is touched.The Problem With Component-Level Reactivity
count = 0
active = falseDashboard
count changes from 0 to 1, the only thing that needs to update is the Stat component's value prop. But in a framework with component-level reactivity, the entire dashboard re-renders: all 50 elements, all their attributes, all their children. The virtual DOM diff finds that only the Stat changed, but it had to compare everything to discover that.count changes, it updates only the attributes that reference count. Everything else is untouched.How Attribute Tracking Works
<div
class="card {if active then 'active' else ''}"
style="opacity: {opacity}; transform: translateX({offset}px)"
data-count={count}
>class -> [active]
style -> [opacity, offset]
data-count -> [count]active changes, only the class binding is re-evaluated. When opacity changes, only the style binding is re-evaluated. When count changes, only the data-count binding is re-evaluated.pub struct ReactiveBinding {
pub element_id: usize,
pub attribute: String,
pub expression: CompiledExpr,
pub dependencies: Vec<VariableId>,
}notify_change function is O(k) where k is the number of bindings that depend on the changed variable. For a typical component, k is 1-5. For a complex component with 50 elements, k is still 1-5 -- because most elements do not depend on any given variable.Text Content Reactivity
count = 0
<p>You have clicked {count} times</p>"You have clicked {count} times" depends on count. When count changes, the text node's content is updated. The element itself is not touched. Other text nodes in the component are not touched.Two-Way Binding
value attribute:name = ""
<input value={name} />
// Typing "Juste" updates name to "Juste"
// Setting name = "Claude" updates the input displayname. When name changes (from code), the reactive binding updates the input's value attribute.value and onChange) and more explicit than Vue's v-model (which is syntactic sugar for the same thing). In FLIN, value={name} is a bidirectional binding by default for input elements.value on an , , or element should be bidirectional and generates both the read binding (variable -> attribute) and the write binding (input event -> variable).Conditional Rendering Reactivity
{if}) are reactive at the block level:show_details = falseshow_details changes from false to true, the {if} block creates the Card and its children. When it changes back to false, the Card is removed. The Button's text content updates through attribute reactivity (the {show_details ? "Hide" : "Show"} expression depends on show_details).{if} block does not re-evaluate its children on every render. It mounts the children once when the condition becomes true and unmounts them when it becomes false. While mounted, the children have their own reactive bindings that update independently.List Rendering Reactivity
{for}) tracks items by identity:items = ["Apple", "Banana", "Cherry"]
{for item in items}
```items changes (an item is added, removed, or reordered), FLIN computes the minimal set of DOM operations: element elementkey prop. FLIN uses the item's identity (its reference or value) as the implicit key. For entity instances, the id field is used as the key. For primitive values, the value itself is the key.// Entity items use id as key automatically
{for user in users}
<UserCard user={user} />
{/for}
// Adding a user creates one UserCard
// Removing a user removes one UserCard
// Reordering users moves existing UserCardsBatched Updates
fn handle_submit() {
name = form_name // State change 1
email = form_email // State change 2
submitted = true // State change 3
error = none // State change 4
}Computed Values
items = [1, 2, 3, 4, 5]
total = items.sum // Reactive: updates when items changes
average = total / items.len // Reactive: updates when total or items changes
display = "Total: {total}, Avg: {average}" // Reactive: updates when total or average changesitems -> total -> display, and items -> average -> display. When items.push(6) is called, total is recomputed, average is recomputed, display is recomputed, and the Text element's content is updated. All in a single synchronous pass, with no unnecessary intermediate renders.Performance Characteristics
Comparison: Reactivity Granularity
Framework Reactivity Granularity Re-render Unit React Component Full component (VDOM diff) Vue Component Component (VDOM diff, with optimization) Svelte Statement Assignment targets SolidJS Signal Individual DOM nodes FLIN Attribute Individual DOM attributes createSignal calls and getter/setter patterns. FLIN's reactivity is implicit -- assign to a variable, and everything that depends on it updates automatically.Why This Matters
Responses
flin
Attribute Reactivity
How FLIN's fine-grained reactivity system tracks dependencies at the attribute level -- updating only the specific DOM attributes that change, not entire components.
Thales & Claude | March 25, 2026 9 min flin
flinreactivityattributesbinding
in FLIN, two things happen when
{active ? "Active" : "Inactive"}
```
```
Share this article:
Loading responses...
Related Articles
Thales & Claude sh0
34 Rules to Catch Deployment Mistakes Before They Happen
We built a pure-Rust static analysis engine with 34 rules across 8 categories to catch security issues, misconfigurations, and deployment mistakes before they reach production.
12 min Mar 25, 2026
ruststatic-analysissecuritycode-health +2
Claude flin
FLIN: The Language That Replaces 47 Technologies
One language for frontend, backend, database, and tooling. Built from scratch in Rust with 3,200+ tests. No npm. No Webpack. No framework fatigue.
4 min Mar 25, 2026
flinrustprogramming-languagecompiler +2
Thales & Claude sh0
Auto-Detecting 19 Tech Stacks from Source Code
How sh0's build engine detects 19 tech stacks, generates production-grade Dockerfiles with multi-stage builds, and creates optimized build contexts -- all in pure Rust.
11 min Mar 25, 2026
ruststack-detectiondockerfilebuild-engine +2