@hasan894/htss
v1.0.1
Published
htss is an esoteric language like css that transpiles to html
Downloads
34
Maintainers
Readme
HTSS
HTSS (HTML Style Sheet) is a custom templating language that looks like CSS but compiles to HTML. Write your markup in a clean, expressive syntax inspired by CSS selectors — variables, conditionals, loops, components, and string interpolation — and get valid HTML out.
$name: "World";
div.hero {
data: "Hello $name";
@for $i from 1 to 3 {
span { data: "Item $i"; }
}
}Compiles to:
<div class="hero">
Hello World
<span>Item 1</span>
<span>Item 2</span>
<span>Item 3</span>
</div>Table of Contents
Installation
npm
npm install @hasan894/htssimport { HTSS } from "@hasan894/htss";
const html = HTSS.run(`h1 { data: "Hello"; }`);
console.log(html);Browser (CDN)
No build step needed. Import directly from jsDelivr:
<script type="module">
import { HTSS } from "https://cdn.jsdelivr.net/npm/@hasan894/[email protected]/dist/index.js";
document.body.innerHTML = HTSS.run(`
h1 { data: "Hello from HTSS!"; }
`);
</script>Quick Start
import { HTSS } from "@hasan894/htss";
const code = `
$title: "My Page";
body {
h1 { data: $title; }
p { data: "Welcome to HTSS."; }
}
`;
// Compile to a full HTML document (with <!DOCTYPE html> wrapper)
const html = HTSS.run(code, false);
console.log(html);HTSS.run(code, debug=false)
Compiles HTSS source code all the way to an HTML string.
| Param | Type | Description |
|---------|-----------|------------------------------------------------------------------------------|
| code | string | The HTSS source code to compile. |
| debug | boolean | When true, prints the token stream, AST, and transformed node tree to the console. |
Returns a string containing a complete HTML document.
HTSS.AST(code, debug)
Returns the raw Abstract Syntax Tree (a BlockNode) without generating HTML. Useful for tooling and analysis.
Language Reference
Selectors
Selectors mirror CSS syntax and define an HTML element. Every selector maps to one HTML tag.
div { }Classes
Attach one or more classes with .:
div.card { }
div.card.featured { }Renders as <div class="card featured">.
IDs
Attach an id with #:
section#main { }Renders as <section id="main">.
Variable Classes and IDs
Classes and IDs can reference variables using $:
$theme: "dark";
$index: 1;
div.$theme#$index { }Renders as <div class="dark" id="1">.
Nesting
Selectors nest directly inside other selectors to create parent–child relationships:
ul {
li { data: "First"; }
li { data: "Second"; }
}<ul>
<li>First</li>
<li>Second</li>
</ul>Nesting depth is unlimited.
Self-Closing Tags
The following tags are automatically rendered self-closing when they have no text or children: area, base, br, col, embed, hr, img, input, link, meta, source, track, wbr.
br { }
input { type: "text"; }<br />
<input type="text" />Properties & Attributes
Any key–value pair written inside a selector becomes an HTML attribute:
a {
href: "https://example.com";
target: "_blank";
}<a href="https://example.com" target="_blank"></a>The semicolon at the end of a property is optional but recommended.
Boolean Attributes
If a property value evaluates to true, the attribute is emitted as a bare presence attribute. If it evaluates to false, the attribute is omitted entirely.
input {
type: "checkbox";
checked: true;
disabled: false;
}<input type="checkbox" checked />Text Content
The special property data sets the inner text of an element:
h1 { data: "Hello, World!"; }
p { data: "A paragraph."; }<h1>Hello, World!</h1>
<p>A paragraph.</p>Text content is automatically HTML-escaped — characters like <, >, &, and " are safe to use in data values.
When an element has both a data value and child selectors, the text appears before the children.
Variables
Variables are declared with $ and assigned with :. They can hold strings, numbers, or booleans.
$count: 10;
$label: "Items";
$active: true;Variables are scoped: a variable declared inside an element body is not accessible outside it, but inner scopes can read and mutate variables from outer scopes.
$x: 1;
div {
$y: 2; /* $y is local to this div */
span { data: $x; } /* $x is accessible here */
$x: $x + 1; /* mutates the outer $x */
}
/* $x is now 2 here; $y is not accessible */
h1 { data: $x; }Reassigning a variable uses the same syntax as declaring one:
$i: 0;
$i: $i + 1; /* $i is now 1 */String Interpolation
Embed variable values directly inside strings using $variableName:
$name: "Alice";
$age: 30;
p { data: "Hello, $name! You are $age years old."; }<p>Hello, Alice! You are 30 years old.</p>Multiple variables can appear in the same string:
$first: "John";
$last: "Doe";
h2 { data: "Welcome, $first $last."; }Expressions & Operators
Expressions can appear as the value of any property or variable.
Arithmetic
| Operator | Operation | Example | Result |
|----------|----------------|---------------|--------|
| + | Addition / Concat | 2 + 3 | 5 |
| - | Subtraction | 10 - 4 | 6 |
| * | Multiplication | 3 * 4 | 12 |
| / | Division | 10 / 2 | 5 |
| % | Modulo | 7 % 3 | 1 |
+ also concatenates strings and numbers:
$i: 3;
div { style: "font-size: " + $i * 10 + "px"; }
/* → style="font-size: 30px" */Standard operator precedence applies: *, /, % bind tighter than +, -. Use parentheses to override:
div { data: (2 + 3) * 4; } /* → 20 */
div { data: 2 + 3 * 4; } /* → 14 */Comparison
Used primarily in @if and @loop conditions:
| Operator | Meaning |
|----------|-----------------------|
| == | Equal |
| != | Not equal |
| < | Less than |
| > | Greater than |
| <= | Less than or equal |
| >= | Greater than or equal |
Logical
| Operator | Meaning |
|----------|---------|
| && | AND |
| \|\| | OR |
| ! | NOT |
@if $logged-in && !$banned {
div { data: "Welcome back!"; }
}Conditionals
@if
Renders its body only when the condition is truthy:
$score: 85;
@if $score >= 90 {
p { data: "Grade: A"; }
}@if / @else
$dark: true;
@if $dark {
body { class: "theme-dark"; }
} @else {
body { class: "theme-light"; }
}@if / @elif / @else
Chain multiple conditions with @elif. They are evaluated in order and only the first truthy branch is rendered:
$score: 72;
@if $score >= 90 {
p { data: "A"; }
} @elif $score >= 80 {
p { data: "B"; }
} @elif $score >= 70 {
p { data: "C"; }
} @else {
p { data: "F"; }
}@elif chains can be arbitrarily long.
Conditionals Inside Elements
@if can appear inside a selector body to conditionally add children or attributes:
$premium: true;
div.card {
data: "Product";
@if $premium {
span { data: "★ Premium"; }
}
}While Loop (@loop)
@loop repeats its body for as long as a condition is true. Use it like a while loop — you are responsible for mutating the variable that drives the condition, otherwise compilation will throw a runaway loop error (limit: 10,000 iterations).
$i: 1;
@loop $i <= 5 {
p { data: $i; }
$i: $i + 1;
}<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>@loop also works inside element bodies:
$count: 3;
$j: 0;
ul {
@loop $j < $count {
li { data: "Item $j"; }
$j: $j + 1;
}
}For Loop (@for)
@for iterates over an inclusive integer range and automatically manages the iterator variable. No manual increment needed.
@for $<iterator> from <start> to <end> { ... }@for $i from 1 to 5 {
div { data: "Row $i"; }
}<div>Row 1</div>
<div>Row 2</div>
<div>Row 3</div>
<div>Row 4</div>
<div>Row 5</div>start and end can be any numeric expression, including variable references:
$start: 2;
$end: 6;
@for $n from $start to $end {
span { data: $n; }
}The iterator variable is scoped to each loop iteration. Combining @for with conditionals and dynamic selectors is a natural fit:
@for $i from 1 to 6 {
div.card#$i {
data: "Card $i";
@if $i % 2 == 0 {
span { data: "even"; }
} @else {
span { data: "odd"; }
}
}
}Components
Components let you define reusable blocks of markup with named parameters, similar to template partials or functions.
Defining a Component
@component Card($title, $body) {
div.card {
h2 { data: $title; }
p { data: $body; }
}
}Parameters are declared as $name inside parentheses. A component definition produces no output on its own.
Using a Component
@use Card {
title: "Hello";
body: "This is the card body.";
}<div class="card">
<h2>Hello</h2>
<p>This is the card body.</p>
</div>Props are matched by name and evaluated in the caller's scope, so you can pass variable values:
$heading: "Welcome";
$copy: "Some text here.";
@use Card {
title: $heading;
body: $copy;
}Components in Loops
Combining @use with @for or @loop is a powerful way to render lists:
@component Item($n) {
li { data: "Item $n"; }
}
ul {
@for $i from 1 to 4 {
@use Item { n: $i; }
}
}<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
</ul>Components can be defined at the top level or inside an element body (local components).
Comments
Block Comments
/* This entire block is ignored by the compiler */
$x: 10; /* inline block comment */Line Comments
// This line is ignored
$y: 20; // trailing line commentBoth styles are stripped at the lexer stage and produce no output.
API Reference
HTSS.run(code: string, debug: boolean): string
Runs the full compilation pipeline — lexing, parsing, transformation, and HTML generation — and returns a complete HTML document string wrapped in <!DOCTYPE html><html>...</html>.
const html = HTSS.run(`h1 { data: "Hello"; }`);HTSS.AST(code: string, debug: boolean): BlockNode
Returns the parsed Abstract Syntax Tree without generating HTML. Useful for inspection, tooling, or writing custom transformations on top of HTSS.
const ast = HTSS.AST(`div { }`);
console.log(ast); // { type: "Block", body: [...] }debug mode
When debug is true in either method, the following are printed to console:
- Lexer output — the full token stream with types and values
- Parser output — the full AST in JSON format
- Transformer output — the resolved
HTMLElementNodetree - Final HTML — the generated HTML string
This is useful for understanding exactly how your code is being interpreted and for troubleshooting unexpected output.
Examples
Navigation Bar
@component NavLink($label, $url) {
li {
a {
href: $url;
data: $label;
}
}
}
nav {
ul {
@use NavLink { label: "Home"; url: "/"; }
@use NavLink { label: "About"; url: "/about"; }
@use NavLink { label: "Contact"; url: "/contact"; }
}
}Conditional Theme
$light-theme: false;
body {
@if $light-theme {
div { class: "bg-white text-black"; data: "Light Mode"; }
} @else {
div { class: "bg-gray-900 text-white"; data: "Dark Mode"; }
}
}Error Reference
HTSS throws descriptive errors with line and column numbers.
| Error | Cause |
|-------|-------|
| Unterminated string at L:C | A string literal was opened with " but never closed. |
| Unterminated comment at L:C | A block comment /* was opened but */ was never found. |
| Unknown directive @name at L:C | An @ keyword other than if, elif, else, loop, for, component, or use was used. |
| Invalid character 'x' at L:C | A character the lexer doesn't recognise was encountered. |
| Unexpected '=' — did you mean '=='? | A single = was found; HTSS uses == for equality comparison. |
| Undefined variable: $name | A variable was referenced before it was declared in any accessible scope. |
| Unknown component: @use Name | @use was called for a component that was never defined with @component. |
| Component Name: missing prop "$param" | A required parameter was not provided in the @use prop block. |
| @loop exceeded 10000 iterations | The loop condition never became false. Check your increment logic. |
| @for exceeded 10000 iterations | The @for range is too large (> 10,000 steps). |
| @for bounds must be numbers | The from or to expression did not evaluate to a number. |
License
ISC
