RFC: v0.2 phase 3 — style block, behavior verbs, components
Part of umbrella #80. Depends on phase 1 (#91) and phase 2 (#92). Largest surface, highest risk.
Summary
Three related primitives:
style block with intent selectors (by role / variant, never by route path).
- Behavior verbs: closed vocabulary of imperative verbs on
on <event>: blocks that compile to a small generated JS bundle.
component <name>: parametrized, stateless render template (rename pending, see open questions).
1. Style block with intent selectors
Intent selectors address elements by their semantic role or variant, not by route path or class name.
style
variant primary
bg: blue-600
fg: white
radius: md
variant danger
bg: red-600
fg: white
role heading
size: xl
weight: bold
role metric
size: 2xl
tabular: true
Closed value scales:
- Color tokens (e.g.,
blue-600, red-600, neutral-100).
- Spacing tokens (0–10).
- Size enum (
xs / sm / md / lg / xl / 2xl).
Raw CSS escape hatch, consistent with query: """ ... """ for SQL:
style
raw: """
@keyframes spin { from { transform: rotate(0) } to { transform: rotate(360deg) } }
"""
Rejected alternative from the original RFC: selecting by route path (action /tasks/:id/delete) couples visual styling to URL structure. Renaming a route would silently break styling. Dropped in favor of role/variant only.
2. Behavior verbs
Closed imperative vocabulary attached via on <event>: blocks. Compiles to a small generated JS bundle served next to htmx.min.js.
Events: click, submit, success, error, hover, focus, blur.
Verbs: show, hide, toggle, toast, redirect, swap, focus, disable, enable, confirm, copy, wait, then.
Animation verbs: fade, slide, bounce, pulse, shake.
button action="/tasks/{t.id}/delete" confirm
"Delete"
on success
toast "Task deleted"
fade self
3. Components (stateless render templates)
component task_card t
row card pad=3 align=center
check t.done action="/tasks/{t.id}/toggle"
text t.title strike=t.done
button action="/tasks/{t.id}/delete" variant=danger size=sm "X"
page /tasks
query tasks: SELECT * FROM task WHERE owner = :current_user.id
stack pad=4 gap=3
each tasks as t
task_card t
Components have no state and no behavior of their own. They are pure template functions.
Depends on
Open questions
- Naming: "component" vs. "template" / "partial". "Component" will mislead users coming from React, who expect state and lifecycle. Proposal: rename to
template or partial.
- Component parameter typing. Should params be typed against a model definition? Proposal: yes, optional type annotation:
component task_card t: task.
- Theme reuse. Should
style blocks be importable across files for shared design systems? This conflicts with Principle 5 (one file = complete app). Proposal: no cross-file style imports in v0.2; revisit if real demand appears.
- Behavior verb runtime surface. 7 events × 14 verbs × 5 animations is a significant JS codegen surface. Needs cross-browser test matrix, htmx event model compatibility check, and a debugging story.
- Animation verbs. May be out of scope entirely. Candidate for dropping from phase 3 and revisiting when a concrete need surfaces.
Success criteria
kiln-studio rewritten to use style + components; LOC drops measurably vs. current approach.
- Behavior verb JS bundle under 5 KB gzipped.
- No user-written
<script> tags in examples/ except inside script raw: escape hatches.
Decision gate
Do not start phase 3 until phases 1 and 2 have shipped and been used by real apps. The vocabulary choices here depend on concrete pain points observed in practice, not speculation.
Rejected alternatives
- Intent selectors by route path: breaks on route renames.
- Stateful components: scope creep;
socket and stream already cover reactive needs.
- Open-ended behavior verbs (any JS expression): defeats the closed-vocabulary premise.
RFC: v0.2 phase 3 — style block, behavior verbs, components
Part of umbrella #80. Depends on phase 1 (#91) and phase 2 (#92). Largest surface, highest risk.
Summary
Three related primitives:
styleblock with intent selectors (byrole/variant, never by route path).on <event>:blocks that compile to a small generated JS bundle.component <name>: parametrized, stateless render template (rename pending, see open questions).1. Style block with intent selectors
Intent selectors address elements by their semantic role or variant, not by route path or class name.
Closed value scales:
blue-600,red-600,neutral-100).xs/sm/md/lg/xl/2xl).Raw CSS escape hatch, consistent with
query: """ ... """for SQL:Rejected alternative from the original RFC: selecting by route path (
action /tasks/:id/delete) couples visual styling to URL structure. Renaming a route would silently break styling. Dropped in favor ofrole/variantonly.2. Behavior verbs
Closed imperative vocabulary attached via
on <event>:blocks. Compiles to a small generated JS bundle served next tohtmx.min.js.Events:
click,submit,success,error,hover,focus,blur.Verbs:
show,hide,toggle,toast,redirect,swap,focus,disable,enable,confirm,copy,wait,then.Animation verbs:
fade,slide,bounce,pulse,shake.3. Components (stateless render templates)
Components have no state and no behavior of their own. They are pure template functions.
Depends on
action=attribute #91,action=) landed.Open questions
templateorpartial.component task_card t: task.styleblocks be importable across files for shared design systems? This conflicts with Principle 5 (one file = complete app). Proposal: no cross-file style imports in v0.2; revisit if real demand appears.Success criteria
kiln-studiorewritten to usestyle+ components; LOC drops measurably vs. current approach.<script>tags inexamples/except insidescript raw:escape hatches.Decision gate
Do not start phase 3 until phases 1 and 2 have shipped and been used by real apps. The vocabulary choices here depend on concrete pain points observed in practice, not speculation.
Rejected alternatives
socketandstreamalready cover reactive needs.