GOWDK has an initial compile-time CSS extension point. It is intentionally small: Tailwind and other CSS tools are addons or plugins, not core dependencies.
SPA builds discover CSS files by filename. A file named forms.css exports
the CSS input forms; a file named blog.post.css exports blog.post.
Configure discovery when the default **/*.css scan is too broad:
var Config = gowdk.Config{
CSS: gowdk.CSSConfig{
Include: []string{"styles/**/*.css"},
Exclude: []string{"styles/old.css"},
},
}When a page omits @css, GOWDK behaves as if the page declared:
@css default page
Built-in CSS inputs:
default:CSS.Default, orglobalwhenglobal.cssexists.page: CSS matching the page ID, such asblog.post.cssfor@page blog.post.none: no GOWDK-managed page CSS, and it must be used alone.
Examples:
@route "/blog/{slug}"
@guard public
Uses global.css plus page-ID CSS when those files exist. With a derived page
ID from blog-post.page.gwdk, the page-specific file is blog-post.css; with
explicit @page blog.post, it is blog.post.css.
@route "/dashboard"
@guard public
@css reset tokens forms
Uses only reset.css, tokens.css, and forms.css.
@route "/embed"
@guard public
@css none
Disables discovered page CSS inputs. A sibling style {} block still emits
generated CSS for the page.
Generated page CSS defaults to:
assets/gowdk/<page-id>.css
That path is the logical asset name. Build output writes a minified,
content-hashed physical filename such as
assets/gowdk/home.46d269b964e6.css, updates generated stylesheet links to the
hashed URL, and records the logical-to-emitted mapping in gowdk-assets.json.
Change the output path and href prefix with:
var Config = gowdk.Config{
CSS: gowdk.CSSConfig{
Output: gowdk.CSSOutputConfig{
Dir: "assets/pages",
HrefPrefix: "/assets/pages",
},
},
}Pages, components, and layouts can declare a sibling style {} block outside
view {}:
view {
<main class="hero">Home</main>
}
style {
.hero {
color: red;
}
}
The style block is parsed separately from the view body. GOWDK emits the CSS as
generated CSS assets instead of inline <style> tags:
- Page style blocks are appended to that page's generated CSS asset.
- Component style blocks are scoped like component
@cssfiles. - Layout style blocks are linked by pages that declare that layout.
SPA builds emit literal stylesheet links from BuildConfig.Stylesheets:
var Config = gowdk.Config{
Build: gowdk.BuildConfig{
Stylesheets: []gowdk.Stylesheet{
{Href: "/assets/app.css"},
},
},
}Generated pages include:
<link rel="stylesheet" href="/assets/app.css">href values are HTML-escaped.
Component CSS is explicit. A component declares stylesheet inputs with
component-local @css annotations:
package components
@component Hero
@css "./hero.css"
view {
<section class="hero">...</section>
}
The path is relative to the component source file. GOWDK parses the annotation
and lowers it into the typed .gwdk AST and stable IR with owner metadata,
source path, CSS path, deterministic hash key, and deterministic scope ID.
See examples/components/css/ for a component-local CSS metadata example.
Component assets use the same explicit model:
package components
@component Hero
@asset "./hero.png"
@asset paths are also relative to the component source file. GOWDK emits
those files under assets/gowdk/components/<package>/<component>/ with
content-hashed filenames, records the logical-to-emitted mapping in
gowdk-assets.json, and serves them from generated binaries with immutable
cache headers. GOWDK does not implicitly bundle arbitrary sibling files.
Current implementation status:
- Page CSS discovery, page
style {}CSS, configured stylesheets, processor-emitted CSS, minified hashed filenames, asset manifest mappings, and generated binary cache headers are implemented for build output. - Component
@cssannotations are parsed, analyzed, emitted as scoped CSS files, content-hashed, linked from generated pages, recorded ingowdk-assets.json, and served with immutable generated binary cache headers. Componentstyle {}CSS uses the same scoped emission path. - Component
@assetannotations are parsed, analyzed, emitted as content-hashed files, recorded ingowdk-assets.json, and served with immutable generated binary cache headers. - Full component AST bodies are not yet passed to CSS processors. Processors receive source metadata and the current extracted class subset.
The component CSS scoping contract is:
- Component CSS is scoped by default when emitted by GOWDK.
- The generated scope marker comes from the component CSS scope ID and is attached to compiler-owned component output.
- Local selectors are rewritten with the generated scope marker. Rewriting must
avoid surprising specificity changes; when browser support allows it, GOWDK
uses
:where(...)around the generated scope marker so scoping adds no extra selector specificity. - Local
@keyframesnames are scoped with the same scope ID, and localanimationandanimation-namereferences are rewritten to the scoped keyframe name. - Global CSS does not leak out of component CSS by accident. Use page/global
CSS for application-wide styles. A future explicit
:global(...)escape can be added, but implicit global selectors in component CSS are not part of the contract. - Emitted component CSS and
@assetfiles are content-hashed, recorded ingowdk-assets.json, and served with the same generated binary cache policy as other immutable emitted assets.
Relationship to other CSS features:
- Page CSS is the implemented build-output path today.
- Component CSS is the component-local authoring and emitted build-output path today.
- CSS processors and Tailwind are optional. They can operate on discovered source metadata and emitted assets, but they must not become mandatory core dependencies.
- Generated filenames are content-hashed for emitted GOWDK-managed CSS assets. Logical-to-emitted mappings and cache headers are recorded in the asset manifest.
Compile-time CSS plugins implement:
type CSSProcessor interface {
gowdk.Addon
ProcessCSS(gowdk.CSSContext) (gowdk.CSSResult, error)
}CSSContext includes:
Sources: discovered page/component source metadata.Sources[*].CSSClasses: extracted literal class names from the current view subset.OutputDir: the current build output directory.Build: the active build config.CSS: the active CSS config.
Component @css annotations are represented in the typed .gwdk AST and
stable IR with deterministic owner, scope ID, and hash-key metadata for future
scoping and emitted filename decisions. Full component AST bodies are not yet
passed to CSS processors; processors should use source metadata and extracted
classes in the current contract. CSSResult can return global stylesheet
links, page-specific stylesheet links through PageStylesheets, and CSS
assets. Page-specific stylesheet map keys must match known page IDs. CSS asset
paths must be relative and stay inside the output directory.
Processor-emitted CSS files are recorded in gowdk-assets.json:
{
"version": 1,
"files": {
"assets/app.css": "assets/app.7ada5a1234b1.css"
},
"hashes": {
"assets/app.css": "sha256:7ada5a1234b1..."
},
"cache": {
"assets/app.css": "public, max-age=31536000, immutable"
}
}GOWDK minifies processor-emitted and discovered page CSS before hashing. A
processor stylesheet link such as /assets/app.css is rewritten to the hashed
emitted file when the processor also emits assets/app.css.
Configured stylesheet links are not asset manifest entries unless a processor also emits the referenced file.
addons/css.Addon() registers the css feature. Real processors can implement
gowdk.CSSProcessor directly or use the aliases in addons/css.
addons/tailwind provides an experimental Tailwind v4-oriented processor that
uses the standalone CLI:
Addons: []gowdk.Addon{
tailwind.Addon(tailwind.Options{
Input: "assets/app.css",
Minify: true,
}),
}Defaults:
Command: usetailwindcssfromPATH.OutputPath:assets/app.cssHref:/assets/app.css
At build time the addon creates a temporary Tailwind input file that imports the
configured Input CSS and adds @source declarations for discovered GOWDK
source files. This follows Tailwind v4's CSS/source directive model. It then
runs the standalone executable with -i <temp-input> -o <temp-output>.
The addon does not use npm, run npx, or run through a shell. If Command is
omitted and tailwindcss is not available on PATH, gowdk build fails with
an install-required error. GOWDK does not download Tailwind. Install Tailwind
through your approved toolchain and use an explicit executable when builds need
a pinned binary:
tailwind.Addon(tailwind.Options{
Input: "assets/app.css",
Command: ".gowdk/bin/tailwindcss",
})Minimal setup:
mkdir -p assets
printf '@import "tailwindcss";\n' > assets/app.css
tailwindcss --help >/dev/nullWindows PowerShell setup:
New-Item -ItemType Directory -Force assets
Set-Content -Path assets/app.css -Value '@import "tailwindcss";'
tailwindcss --help | Out-NullThen configure GOWDK:
package main
import (
"github.com/cssbruno/gowdk"
"github.com/cssbruno/gowdk/addons/tailwind"
)
var Config = gowdk.Config{
Addons: []gowdk.Addon{
tailwind.Addon(tailwind.Options{
Input: "assets/app.css",
Minify: true,
}),
},
}Build output, generated app output, and generated binaries all receive the processor-emitted stylesheet link and generated CSS asset:
gowdk build --out dist/site
gowdk build --out dist/site --app .gowdk/app --bin bin/siteThe literal gowdk.config.go parser supports this known literal constructor
shape when tailwind is imported from
github.com/cssbruno/gowdk/addons/tailwind. It also recognizes built-in
no-argument addon constructors. External CSS processor addons can be imported
from other Go modules; when the AST-only loader cannot reduce the constructor,
GOWDK uses the executable config bridge and proxies ProcessCSS back to that
importable addon.
GOWDK does not generate Tailwind v3 content configuration in this slice.