Render a table of records, with a column that styles itself from the value it is showing.
Loops over an array of objects, reads properties with ordinary dot notation, and formats numbers with toFixed. Nothing here is template syntax — it is the same JavaScript you would write anywhere else, evaluated where it stands.
<style>
body { font: 14px/1.5 system-ui, sans-serif; margin: 0; color: #24292f; }
table { border-collapse: collapse; width: 100%; }
th, td { padding: 6px 10px; border-bottom: 1px solid #d8dee4; text-align: left; }
th { font-weight: 600; background: #f6f8fa; }
td.num { text-align: right; font-variant-numeric: tabular-nums; }
.slow { color: #cf222e; font-weight: 600; }
</style>
<script type="text/template" id="services">
<table>
<thead>
<tr>
#for(let col of columns) {#
<th>#col#</th>
#}#
</tr>
</thead>
<tbody>
#for(let s of services) {#
<tr>
<td>#s.name#</td>
<td>#s.region#</td>
<td class="num #s.latency > 200 ? 'slow' : ''#">#s.latency# ms</td>
<td class="num">#s.uptime.toFixed(2)# %</td>
</tr>
#}#
</tbody>
</table>
</script>
<div id="output"></div>
<script src="https://cdn.jsdelivr.net/gh/richdafunk/hashJS@v1.3.6/hashJS.js"></script>
<script>
const data = {
columns: ['Service', 'Region', 'Latency', 'Uptime'],
services: [
{ name: 'api-gateway', region: 'eu-north-1', latency: 42, uptime: 99.983 },
{ name: 'auth', region: 'eu-north-1', latency: 18, uptime: 99.992 },
{ name: 'image-resize', region: 'eu-west-1', latency: 310, uptime: 99.421 },
{ name: 'search', region: 'us-east-1', latency: 87, uptime: 99.910 }
]
};
new hashJS('services', data, 'output');
</script> Two things are worth noticing.
The header row is driven by a separate columns array, so the table's shape is data rather than markup. The body reads named properties, which keeps each cell self-describing and lets columns be reordered without hunting through indexes.
The latency cell carries an expression inside an attribute: class="num #s.latency > 200 ? 'slow' : ''#". Expressions are not restricted to text nodes — they are substituted wherever they appear, including inside attribute values, because compilation happens on the template string before any of it becomes markup.
The template lives in a <script type="text/template"> block. That matters here: a browser would otherwise escape the > in the comparison before the library ever saw it.