A SwiftUI-inspired static site generator for Dart. Compose HTML pages using typed, composable functions — no raw strings, no templates, no fuss.
Add to your pubspec.yaml:
dependencies:
hasty: ^0.0.2Then run:
dart pub getimport 'package:hasty/hasty.dart';
void main() async {
final page = vStack(
id: 'app',
style: Style(fontFamily: 'sans-serif', padding: 24, maxWidth: 800),
children: [
h1('Hello, Hasty!', style: Style(color: Colors.indigo)),
p('A page built entirely in Dart.'),
],
);
await emit(page, title: 'Hello Hasty'); // writes build/index.html
}Everything is a Node — a lightweight representation of an HTML element. Tag functions like div, h1, and p return nodes that you compose into a tree.
Hasty provides Flutter-style layout primitives that map to CSS flexbox:
hStack(
children: [
h1('My Site'),
spacer(), // expands to push nav to the right
nav(children: [
hStack(style: Style(gap: 16), children: [
a(href: '#about', content: 'About'),
a(href: '#contact', content: 'Contact'),
]),
]),
],
)| Function | HTML output | Description |
|---|---|---|
vStack |
<div> (flex column) |
Vertical stack |
hStack |
<div> (flex row) |
Horizontal stack |
zStack |
<div> (relative) |
Layered stack (children use absolute) |
spacer |
<div> (flex: 1) |
Expands to fill remaining space |
padding |
<div> |
Wraps a child with configurable padding |
center |
<div> (flex center) |
Centers a child horizontally/vertically |
Both hStack and vStack accept MainAxisAlignment and CrossAxisAlignment to control child alignment.
Use the Style class for type-safe inline CSS — no raw strings needed:
Style(
color: Colors.indigo,
backgroundColor: Colors.white,
padding: 16,
borderRadius: 8,
border: '1px solid ${Colors.lightGray}',
fontWeight: FontWeight.semibold,
textAlign: TextAlign.center,
)Colors — Colors.black, Colors.white, Colors.red, Colors.orange, Colors.yellow, Colors.green, Colors.teal, Colors.blue, Colors.indigo, Colors.purple, Colors.pink, Colors.gray, Colors.lightGray, Colors.darkGray, Colors.transparent
Custom colors: Color.hex('#ff6600'), Color.rgb(255, 102, 0), Color.rgba(255, 102, 0, 0.5)
FontWeight — thin, light, normal, medium, semibold, bold, extrabold
Enums — TextAlign, TextDecoration, Position, Display, Float, FlexWrap, AlignSelf
h1 h2 h3 h4 h5 h6
p span strong em — and text(content, kind: TextKind.caption) for the full set (h1–h6, body, caption, label)
div section article main_ header footer nav
ul(children: [
li('First item'),
li('Second item'),
])a(href: 'https://example.com', content: 'Visit', target: '_blank')
button('Submit', type: 'submit')img(src: 'photo.png', alt: 'A photo') hr() br()
form(
action: '/contact',
method: 'post',
children: [
label(htmlFor: 'email', content: 'Email'),
input(type: 'email', id: 'email', name: 'email', required: true),
textarea(name: 'message', placeholder: 'Your message', rows: 5),
select(name: 'topic', children: [
option(value: 'support', content: 'Support'),
option(value: 'sales', content: 'Sales', selected: true),
]),
button('Send', type: 'submit'),
],
)table(children: [
caption('Team'),
thead(children: [
tr(children: [
th(content: 'Name', scope: Scope.col),
th(content: 'Role', scope: Scope.col),
]),
]),
tbody(children: [
tr(children: [td(content: 'Alice'), td(content: 'Engineer')]),
tr(children: [td(content: 'Bob'), td(content: 'Designer')]),
]),
])For trusted content only — bypasses escaping:
raw('<strong>bold</strong>')Never pass user-supplied data to raw.
// Write build/index.html — output is automatically prettified
await emit(page, title: 'My Site');
// Custom output directory, file name, and language
await emit(page, outputDir: 'public', fileName: 'about', title: 'About', lang: 'fr');
// Get the raw HTML string without writing any files
final html = renderHtml(page);emit displays a progress spinner via mason_logger and prints a green ✓ with elapsed time on completion.
Hasty includes a lightweight argument parser for build scripts:
import 'dart:io';
import 'package:hasty/hasty.dart';
void main(List<String> args) async {
final parser = Parser();
parser.register('output', 'Output directory', type: String);
parser.register('--minify', 'Skip formatting');
try {
parser.parse(args);
} catch (e) {
print(e);
exit(1);
}
final outDir = parser.getParsed()['output'] ?? 'build';
await emit(page, outputDir: outDir);
}Flags can be passed as output=public, output:public, or output public.
See example/hasty_example.dart for a complete page demonstrating nav with spacer, a padding/center hero, a ul/li feature list, a specs table, a contact form, and CLI argument parsing.
See bin/hasty.dart for a working blog layout that additionally shows inline text styling with span and FontWeight.