
Security News
The Next Open Source Security Race: Triage at Machine Speed
Claude Opus 4.6 has uncovered more than 500 open source vulnerabilities, raising new considerations for disclosure, triage, and patching at scale.
Paste your html into a template and use functions of your model for the arguments. When you update your model the DOM updates itself. No compiling and nothing is virtual.
h, render, remodelh, a template tagh turns XML with interspersed arguments into a simple object based description of DOM objects.
It's an XML parser with the additional feature that it operates on string literals so that arguments can be copied into the DOM description (where appropriate). This allows render to expand any functions copied into the description itself and allows any web-components to receive real objects as properties.
render it onto the pagerender takes a DOM element and an array of descriptions (generated by h). The descriptions are turned into actual DOM elements and set as children of the passed in element.
import { h, render } from 'horseless'
function fourFiveSix () {
return h`<div>4</div><div>5</div><div>6</div>`
}
render(document.querySelector('.count'), h`
<div>0</div>
<>
<div>1</div>
<div>2</div>
<div>3</div>
</>
${fourFiveSix}
${[7, 8, 9].map(v => h`<div>${v}</div>`)}
`)
Functions used as arguments (fourFiveSix) are evaluated at render time. Arrays are expanded recursively. So if your argument is a function that returns an array of functions that return arrays themselves you still get a flat DOM.
remodel your modelremodel creates a proxy of whatever object you give it. The proxy keeps track of which values were read during renders. If any of those values are changed later the corresponding child node or attribute are updated.
import { h, render, remodel } from 'horseless'
const model = remodel({seconds: 0})
setInterval(() => model.seconds++, 1000)
render(document.body, h`
<span>hello world! seconds running: ${() => model.seconds.toString()}</span>
`)
There's a todomvc example in the docs folder. You can see it running at https://horseless.info/todomvc/
Because functions are used to generate dynamic values, they are always expanded. It's likely that on- handler functions starting with need to be "escaped"
// this works as you'd expect it to
h`<span class=${functionThatReturnsClasses}> click me </span>`
// this logs something like 'click <span>click me</span>' when it's parsed but doesn't do anything when clicked
h`<span onclick=${el => console.log('click', el)}> click me </span>`
// this is probably what you want
h`<span onclick=${el => e => console.log('click', e)}> click me </span>`
put quotes around embedded expression attributes to combine them
h`<span class="${returnsClasses} a-class ${returnsAnotherClass}">
click me
</span>` // this works now
If you come across more things that may be confusing, please file an issue
Well, no... it only handles elements and doesn't check that your tags are valid or anything
let elements = h`
<!DOCTYPE html> // can't do this
<input autofocus> // or this
<!-- comment --> // or this
`
nope nope nope
The first iteration of this project did "just one thing" and that was the template literal xhtml parsing. The model stuff was for demos... but it was so cool (imho) it got moved into the project proper. That said, the goal of this project is to enable transformations from models to views. Having the view update as the model updates seemed to be in line with that goal. So... it's still kind of doing "just one thing" (but even if you don't buy that the two features are deeply integrated and it's still a long way from bloated)
There's around 400 code-golf-free lines with no external dependencies. you can read through all the code and understand every subtle nuance in an hour
The gzipped minified version is 2k
<input autofocus/>)<br>)FAQs
a framework?
The npm package horseless receives a total of 2 weekly downloads. As such, horseless popularity was classified as not popular.
We found that horseless demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Claude Opus 4.6 has uncovered more than 500 open source vulnerabilities, raising new considerations for disclosure, triage, and patching at scale.

Research
/Security News
Malicious dYdX client packages were published to npm and PyPI after a maintainer compromise, enabling wallet credential theft and remote code execution.

Security News
gem.coop is testing registry-level dependency cooldowns to limit exposure during the brief window when malicious gems are most likely to spread.