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.
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
go install github.com/phillip-england/gtml@latestVerify installation:
gtml --helpgtml init mysiteThis creates:
mysite/
components/ # Reusable HTML components
routes/ # Page routes (become HTML files)
static/ # Static assets (CSS, JS, images)
dist/ # Compiled output
components/Greeting.html:
<div props='name string'>
<h1>Hello, {name}!</h1>
</div>routes/index.html:
<html>
<body>
<Greeting name="World" />
</body>
</html>gtml compile mysitedist/index.html:
<html>
<body>
<div data-greeting="">
<h1>Hello, World!</h1>
</div>
</body>
</html>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)
- 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)
- 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
<style>
h1 { color: red; }
</style>
<div props='heading string, subheading string'>
<h1>{heading}</h1>
<p>{subheading}</p>
</div>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>| 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 {} |
Props pass down through nested components:
<div props='title string'>
<ChildComponent text={title} />
</div>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.
Use ternary operators for conditionals:
<div props='isAdmin boolean'>
{ isAdmin ? (
<p>Welcome, Admin!</p>
) : (
<p>Welcome, User!</p>
) }
</div>==(equals)!=(not equals)<(less than)>(greater than)<=(less than or equal)>=(greater than or equal)
&&(and) - both conditions must be true||(or) - at least one condition must be true
<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>Use {} for dynamic values:
<Calculator result={10 + 5 * 2} />
<Greeting name={"Bo" + "b"} />
<User age={currentAge + 1} />| 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 |
Expressions follow PEMDAS order (parentheses, exponents, multiplication/division/modulo, addition/subtraction).
<!-- 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} />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>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
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.
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>| Selector | Description |
|---|---|
#id |
Select element by ID |
.class |
Select element by class |
#id* |
Select all matching elements |
<button id='submit'>Submit</button>
<div id='output'></div>
<script type='gtml'>
#submit.onclick(() => {
#output.innerHTML = "Clicked!"
})
</script><div props='count int'>
<p>Count: {count}</p>
<button onclick={() => { count = count + 1 }}>
Increment
</button>
</div>gtml includes a built-in signal library for reactivity. When signals are modified, the DOM updates automatically.
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='METHOD URL' as='variableName'
METHOD: HTTP method (GET, POST, PUT, DELETE, etc.)URL: Endpoint URLas: Variable name for the response data
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>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>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'
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>Initialize a new gtml project with starter files and preinstalled components.
--force: Overwrite existing directory
Compile all routes to static HTML in the dist directory.
--watch: Watch for changes and recompile automatically
Run component tests. Tests are -test.html files that compile successfully.
- 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
With --watch, gtml monitors all files in the project directory and recompiles on any change.
gtml comes with 80+ ready-to-use Tailwind-styled components.
| 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 |
| 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 |
| 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 |
| Component | Description |
|---|---|
AlertSuccess |
Green success alert |
AlertError |
Red error alert |
AlertWarning |
Yellow warning alert |
AlertInfo |
Blue information alert |
Notification |
Multi-type notification |
| Component | Description |
|---|---|
BadgePrimary |
Blue status badge |
BadgeSuccess |
Green status badge |
BadgeWarning |
Yellow status badge |
BadgeDanger |
Red status badge |
SkillBadge |
Skill level indicator |
| 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 |
| 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 |
| 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 |
| Component | Description |
|---|---|
TableBasic |
Full table wrapper |
TableHeader |
Table header cell |
TableRow |
Table row container |
TableCell |
Table data cell |
| Component | Description |
|---|---|
Divider |
Horizontal divider |
LoadingSpinner |
Loading animation |
LoadingSkeleton |
Skeleton placeholder |
StepIndicator |
Step progress |
StepItem |
Individual step |
| Component | Description |
|---|---|
SocialLinks |
Social media icons |
Tag |
Clickable tag |
TagList |
Multiple tags container |
CodeBlock |
Code snippet display |
CallToAction |
CTA section |
EmptyState |
Zero data display |
| Component | Description |
|---|---|
Dropdown |
Dropdown menu |
DropdownItem |
Dropdown menu item |
ToggleSwitch |
Toggle control |
Tooltip |
Hover tooltip |
SearchInput |
Search with icon |
| Component | Description |
|---|---|
PricingFeature |
Pricing feature item |
Create test files with the -test.html suffix:
routes/component-test.html:
<MyComponent prop='value' />Run tests:
gtml test
# or
make testTests compile the component and verify no errors occur.
go build -o gtml .make test
# or
go test ./...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.
MIT