gtml
An opinionated static site generator with a built-in component system that compiles HTML components into static websites. gtml features reactive props, client-side fetch with suspense/fallback, scoped CSS, and 80+ preinstalled Tailwind-styled components.
What is gtml?
gtml is three things:
- A Static Site Generator - Compiles routes and components into static HTML files
- A Templating System - Create reusable HTML components with props, slots, and expressions
- A Compiler - Transforms component syntax into plain HTML with zero runtime overhead
Quick Start
Installation
go install github.com/phillip-england/gtml@latest
Verify installation:
gtml --help
Initialize a Project
gtml init mysite
This creates:
mysite/
components/ # Reusable HTML components
routes/ # Page routes (become HTML files)
static/ # Static assets (CSS, JS, images)
dist/ # Compiled output
Create a Component
components/Greeting.html:
<div props='name string'>
<h1>Hello, {name}!</h1>
</div>
Use in a Route
routes/index.html:
<html>
<body>
<Greeting name="World" />
</body>
</html>
Compile
gtml compile mysite
Output
dist/index.html:
<html>
<body>
<div data-greeting="">
<h1>Hello, World!</h1>
</div>
</body>
</html>
Project Structure
myproject/
components/ # Reusable components (PascalCase names)
Button.html
Layout.html
ui/ # Subdirectories allowed
Card.html
routes/ # Page routes (kebab-case names only)
index.html # -> dist/index.html
about.html # -> dist/about.html
blog/
post.html # -> dist/blog/post.html
static/ # Static assets (copied to dist/static)
styles.css
script.js
dist/ # Compiled output (generated)
Components Directory Rules
- Naming: PascalCase only (e.g.,
MyComponent.html, NOTmyComponent.html) - Uniqueness: Each component name must be unique across all subdirectories
- One root element: Each file must contain a single root element
- Subdirectories: Allowed (e.g.,
components/ui/Button.html)
Routes Directory Rules
- Naming: kebab-case only (e.g.,
my-route.html, NOTMyRoute.html) - Subdirectories: Allowed (e.g.,
routes/blog/post.html) - Naming collisions: Allowed in different subdirectories
Component System
Basic Component
<style>
h1 { color: red; }
</style>
<div props='heading string, subheading string'>
<h1>{heading}</h1>
<p>{subheading}</p>
</div>
Props
Define typed props on components:
<div props='name string, age int, isAdmin boolean'>
<p>Name: {name}</p>
<p>Age: {age}</p>
{ isAdmin ? (
<p>Admin Access</p>
) : (
<p>User Access</p>
) }
</div>
Prop Types
| Type | Declaration | Usage | Notes |
|---|---|---|---|
string |
name string |
name='Bob' or name={"Bob"} |
Raw strings allowed |
int |
count int |
count={5} or count={2+3} |
Must use {} |
boolean |
enabled boolean |
enabled={true} or enabled={2>1} |
Must use {} |
Prop Drilling
Props pass down through nested components:
<div props='title string'>
<ChildComponent text={title} />
</div>
Slots
Insert content into specific places within components:
Define a slot:
<html props='title string'>
<head><title>{title}</title></head>
<body>
<slot name='content' />
</body>
</html>
Fill a slot:
<Layout title="My Page">
<slot name='content' tag='main' class='container'>
<h1>Welcome!</h1>
</slot>
</Layout>
The tag attribute wraps content in the specified HTML tag. Any other attributes are preserved.
Conditional Rendering
Use ternary operators for conditionals:
<div props='isAdmin boolean'>
{ isAdmin ? (
<p>Welcome, Admin!</p>
) : (
<p>Welcome, User!</p>
) }
</div>
Comparison Operators
==(equals)!=(not equals)<(less than)>(greater than)<=(less than or equal)>=(greater than or equal)
Logical Operators
&&(and) - both conditions must be true||(or) - at least one condition must be true
Nested Ternaries
<div props='age int'>
{ age < 21 ? (
<p>You cannot drink</p>
) : (
{ age > 21 && age < 25 ? (
<p>You can drink but shouldn't</p>
) : (
<p>You can drink</p>
) }
) }
</div>
Expressions
Use {} for dynamic values:
<Calculator result={10 + 5 * 2} />
<Greeting name={"Bo" + "b"} />
<User age={currentAge + 1} />
Supported Operations
| Operator | Types | Example | Result |
|---|---|---|---|
+ |
string + string | "A" + "B" |
"AB" |
+ |
int + int | 2 + 3 |
5 |
- |
int - int | 5 - 2 |
3 |
* |
int * int | 2 * 3 |
6 |
/ |
int / int | 6 / 2 |
3 |
% |
int % int | 5 % 2 |
1 |
Evaluation Order
Expressions follow PEMDAS order (parentheses, exponents, multiplication/division/modulo, addition/subtraction).
Raw Strings vs Expressions
<!-- String - raw value preferred -->
<Component name='Bob' />
<!-- String - expression also works -->
<Component name={"Bob"} />
<!-- Non-string - must use expression -->
<Component count={42} />
<Component enabled={true} />
Styling
Component Styles
Add <style> blocks at the top of components:
<style>
h1 { color: red; }
.card { padding: 1rem; }
</style>
<div props='heading string'>
<h1>{heading}</h1>
<div class="card">Content</div>
</div>
Scoped CSS
Styles are automatically scoped to prevent conflicts:
- Each component gets a unique
data-gtml-scopeattribute - CSS selectors are prefixed to target only the component
- All component styles are aggregated into
dist/static/styles.css
Static Assets
Place global assets in the static/ directory:
static/
styles.css # Global styles
script.js # Global JavaScript
images/ # Images
These are copied to dist/static/ during compilation.
Interactivity
Signals
Props can be treated as reactive signals for client-side interactivity:
<div props='name string'>
<p>My name is {name}!</p>
<button id='btn'>Change Name</button>
</div>
<script type='gtml'>
#btn.onclick(() => {
name = "John"
})
</script>
Element Selection
| Selector | Description |
|---|---|
#id |
Select element by ID |
.class |
Select element by class |
#id* |
Select all matching elements |
Event Handlers
<button id='submit'>Submit</button>
<div id='output'></div>
<script type='gtml'>
#submit.onclick(() => {
#output.innerHTML = "Clicked!"
})
</script>
Inline Events
<div props='count int'>
<p>Count: {count}</p>
<button onclick={() => { count = count + 1 }}>
Increment
</button>
</div>
Signal Library
gtml includes a built-in signal library for reactivity. When signals are modified, the DOM updates automatically.
Fetch System
Basic Fetch
Fetch data from APIs with automatic client-side rendering:
<div fetch='GET /api/users' as='users'>
<ul>
<li for='user in users'>{user.name}</li>
</ul>
</div>
Fetch Syntax
fetch='METHOD URL' as='variableName'
METHOD: HTTP method (GET, POST, PUT, DELETE, etc.)URL: Endpoint URLas: Variable name for the response data
Suspense
Show loading state while fetching:
<div fetch='GET /api/users' as='users'>
<div suspense>
<p>Loading users...</p>
</div>
<ul>
<li for='user in users'>{user.name}</li>
</ul>
</div>
Fallback
Show error state if fetch fails:
<div fetch='GET /api/users' as='users'>
<div fallback>
<p>Failed to load users</p>
</div>
<ul>
<li for='user in users'>{user.name}</li>
</ul>
</div>
For Loops
Iterate over arrays with the for attribute:
<li for='user in users'>{user.name}
<ul>
<li for='color in user.colors'>{color.name}</li>
</ul>
</li>
Syntax: for='item in array' or for='item in parent.nested'
Prop Values in Fetch URLs
Fetch URLs can use prop expressions:
<UserList apiUrl='localhost:8080/api/users'>
<div fetch='GET {apiUrl}' as='users'>
<ul><li for='user in users'>{user.name}</li></ul>
</div>
</UserList>
CLI Commands
gtml init <PATH> [--force]
Initialize a new gtml project with starter files and preinstalled components.
--force: Overwrite existing directory
gtml compile <PATH> [--watch]
Compile all routes to static HTML in the dist directory.
--watch: Watch for changes and recompile automatically
gtml test [PATH]
Run component tests. Tests are -test.html files that compile successfully.
Compilation Process
- Read components directory: Load all components into a registry
- Process routes recursively: Find and compile nested components
- Generate static HTML: Output to
dist/directory - Copy static assets: Copy
static/todist/static/ - Aggregate styles: Combine component styles into
dist/static/styles.css - Inject interactivity: Add signal library and event handlers
Watch Mode
With --watch, gtml monitors all files in the project directory and recompiles on any change.
Preinstalled Components
gtml comes with 80+ ready-to-use Tailwind-styled components.
Buttons
| Component | Description |
|---|---|
ButtonPrimary |
Standard blue primary button |
ButtonSecondary |
Gray secondary button |
ButtonOutline |
Border-only outline button |
ButtonDanger |
Red destructive action button |
ButtonSuccess |
Green positive action button |
ButtonSm |
Small sized button |
ButtonLg |
Large sized button |
Forms
| Component | Description |
|---|---|
InputText |
Standard text input with label |
InputEmail |
Email input field |
InputPassword |
Password input field |
Textarea |
Multi-line text area |
Select |
Dropdown select component |
Checkbox |
Checkbox with label |
RadioGroup |
Radio button group |
FormLayout |
Form wrapper with title/submit |
FormField |
Generic form field wrapper |
LoginForm |
Pre-built login form |
Cards
| Component | Description |
|---|---|
CardBasic |
Simple card with title/content |
CardWithImage |
Card with image header |
CardClickable |
Hoverable clickable card |
ProfileCard |
User profile display |
PricingCard |
Pricing tier display |
Alerts
| Component | Description |
|---|---|
AlertSuccess |
Green success alert |
AlertError |
Red error alert |
AlertWarning |
Yellow warning alert |
AlertInfo |
Blue information alert |
Notification |
Multi-type notification |
Badges
| Component | Description |
|---|---|
BadgePrimary |
Blue status badge |
BadgeSuccess |
Green status badge |
BadgeWarning |
Yellow status badge |
BadgeDanger |
Red status badge |
SkillBadge |
Skill level indicator |
Navigation
| Component | Description |
|---|---|
Navbar |
Top nav bar with brand/links |
NavLink |
Individual nav link |
Breadcrumb |
Breadcrumb container |
BreadcrumbItem |
Individual breadcrumb |
Pagination |
Page navigation controls |
Tabs |
Tab navigation container |
TabItem |
Individual tab |
Layout
| Component | Description |
|---|---|
HeroSection |
Hero/landing with CTA |
Footer |
Site footer |
FooterLink |
Footer link item |
Sidebar |
Sidebar navigation |
SidebarItem |
Sidebar nav item |
Modal |
Modal dialog |
SidebarLayout |
Layout with sidebar |
DashboardLayout |
Dashboard with header |
TwoColumnLayout |
Two column flex |
ThreeColumnLayout |
Three column flex |
Grid2Columns |
Two column grid |
Grid3Columns |
Three column grid |
PageHeader |
Page title with breadcrumbs |
Section |
Generic section wrapper |
Data Display
| Component | Description |
|---|---|
Avatar |
User avatar image/initials |
ProgressBar |
Progress indicator |
Accordion |
Collapsible accordion |
AccordionItem |
Individual accordion item |
ListGroup |
List group container |
ListItem |
List group item |
StatisticCard |
Statistics display |
StatsGrid |
Stats grid container |
StatItem |
Individual statistic |
FeatureCard |
Feature highlight |
TestimonialCard |
Testimonial display |
TeamMemberCard |
Team member profile |
ArticleCard |
Blog/article preview |
Comment |
Comment display |
Tables
| Component | Description |
|---|---|
TableBasic |
Full table wrapper |
TableHeader |
Table header cell |
TableRow |
Table row container |
TableCell |
Table data cell |
Utility
| Component | Description |
|---|---|
Divider |
Horizontal divider |
LoadingSpinner |
Loading animation |
LoadingSkeleton |
Skeleton placeholder |
StepIndicator |
Step progress |
StepItem |
Individual step |
Content
| Component | Description |
|---|---|
SocialLinks |
Social media icons |
Tag |
Clickable tag |
TagList |
Multiple tags container |
CodeBlock |
Code snippet display |
CallToAction |
CTA section |
EmptyState |
Zero data display |
Form Elements
| Component | Description |
|---|---|
Dropdown |
Dropdown menu |
DropdownItem |
Dropdown menu item |
ToggleSwitch |
Toggle control |
Tooltip |
Hover tooltip |
SearchInput |
Search with icon |
Pricing
| Component | Description |
|---|---|
PricingFeature |
Pricing feature item |
Testing
Create test files with the -test.html suffix:
routes/component-test.html:
<MyComponent prop='value' />
Run tests:
gtml test
# or
make test
Tests compile the component and verify no errors occur.
Development
Build
go build -o gtml .
Run Tests
make test
# or
go test ./...
Project Philosophy
gtml uses a "Spec-First" approach where the spec/ directory contains natural language documentation that describes how the system should work. The spec serves as an intermediate representation (IR) that can be used to generate the full implementation.
Links
License
MIT