A Go/templ port of shadcn/ui components that maintains pixel-perfect visual and behavioral parity with minimal JavaScript (lightweight 15KB Datastar library for reactivity).
See datastar-ui.com for component demos.
- π Server-side rendered components with Go/templ
- β‘ Reactive UI powered by Datastar signals
- π¨ Identical styling to shadcn/ui using Tailwind CSS
- π¦ Lightweight - only 15KB Datastar runtime
- π§ Type-safe component args with Go structs
- π Dark mode support built-in
- βΏ Accessible with proper ARIA attributes
- β¨οΈ Keyboard support to tab through forms, arrow keys, enter to select, escape to close, etc.
- Go 1.24+
- Just - command runner
- Air - live reload for Go
- templ - Go templating engine
- tailwindcss standalone CLI
# start the Tailwind CSS watcher:
just tailwind
# start the Go server with live reload:
just watch
see demo site at http://localhost:4242
The development environment will automatically:
- β
Rebuild Go templates when
.templ
files change - β Recompile CSS when Tailwind classes are added/removed
- β Restart the server when Go code changes
datastarui/
βββ components/ # Reusable UI components
β βββ button/ # Button component
β β βββ button.templ # Template file
β β βββ args.go # Component arguments
β β βββ variants.go # CSS class variants
β βββ select/ # Select component (fully refactored)
βββ utils/ # Utility libraries
β βββ signals.go # Signal management with namespacing
β βββ expressions.go # Datastar expression builders
β βββ data_class.go # Conditional CSS class helpers
βββ pages/ # Page templates
β βββ components/ # Component demo pages
βββ main.go # Server entry point
Each component follows a pattern with utility-driven Datastar integration:
- Template File (
dialog.templ
) - Expressions (
expressions.go
) - Args Definition (
args.go
) - CSS Variants (
variants.go
)
package dialog
import "github.com/coreycole/datastarui/utils"
// DialogSignals defines the signal structure for dialog components
type DialogSignals struct {
Open bool `json:"open"`
}
// Dialog container - pure Datastar signal-based modal using data-show
templ Dialog(args DialogArgs) {
{{
// Create signals using the new structured system with proper initial state
signals := utils.Signals(args.ID, DialogSignals{
Open: args.DefaultOpen,
})
// Dialog backdrop overlay
backdropClasses := "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
// Dialog positioning classes - centered on screen
dialogClasses := "fixed left-[50%] top-[50%] z-50 w-full max-w-lg translate-x-[-50%] translate-y-[-50%] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
// Generate the CSS classes for the inner dialog content container using our variant system
containerClasses := DialogVariants(args)
// Create dialog handler for clean expressions
dialogHandler := NewDialogHandler(signals)
backdropClickHandler := dialogHandler.BuildBackdropClickHandler()
escapeHandler := dialogHandler.BuildEscapeHandler()
}}
<div data-signals={ signals.DataSignals }>
<!-- Dialog backdrop overlay -->
<div
data-show={ signals.Signal("open") }
class={ backdropClasses }
data-on-click={ backdropClickHandler }
data-on-keydown__window={ escapeHandler }
if !args.DefaultOpen {
style="display: none;"
}
>
<!-- Dialog content container -->
<div
id={ args.ID }
class={ dialogClasses }
role="dialog"
aria-modal="true"
tabindex="-1"
data-on-click="evt.stopPropagation()"
data-on-mount="evt.target.focus()"
>
<div class={ containerClasses }>
{ children... }
</div>
</div>
</div>
</div>
}
package dialog
import (
"fmt"
"github.com/coreycole/datastarui/utils"
)
// DialogHandler creates handlers for Dialog component functionality
type DialogHandler struct {
signals *utils.SignalManager
}
// NewDialogHandler creates a dialog handler
func NewDialogHandler(signals *utils.SignalManager) *DialogHandler {
return &DialogHandler{
signals: signals,
}
}
// BuildBackdropClickHandler creates the backdrop click handler for closing dialog
func (d *DialogHandler) BuildBackdropClickHandler() string {
return d.signals.ConditionalAction("evt.target === evt.currentTarget", "open", "false")
}
// BuildEscapeHandler creates an escape key handler for closing dialog
func (d *DialogHandler) BuildEscapeHandler() string {
condition := fmt.Sprintf("evt.key === 'Escape' && %s", d.signals.Signal("open"))
return d.signals.ConditionalAction(condition, "open", "false")
}
// BuildCloseHandler creates a close handler with optional return value
func (d *DialogHandler) BuildCloseHandler(returnValue string) string {
expr := utils.NewExpression().Statement(d.signals.Set("open", "false"))
if returnValue != "" {
expr.Statement(d.signals.SetString("returnValue", returnValue))
}
return expr.Build()
}
package dialog
import "github.com/a-h/templ"
// DialogArgs defines the args for the Dialog container (using Datastar signals)
type DialogArgs struct {
ID string
DefaultOpen bool // Whether the dialog should be open by default
Class string
Attributes templ.Attributes
}
// DialogTriggerArgs defines the args for the DialogTrigger component
type DialogTriggerArgs struct {
DialogID string
AsChild bool
Class string
Attributes templ.Attributes
}
package dialog
import (
"github.com/coreycole/datastarui/utils"
)
// DialogVariants returns the CSS classes for the main Dialog container component
func DialogVariants(args DialogArgs) string {
// Dialog-specific styling - optimized for modal dialogs with consistent padding
baseClasses := "max-w-lg w-full max-h-[90vh] overflow-auto bg-background border shadow-lg rounded-lg p-6"
return utils.TwMerge(baseClasses, args.Class)
}
The project uses tailwind classes from shadcn/ui components new york v4.
- Pick a component from the shadcn/ui registry
- Follow the utility-driven architecture using expression builders
- Create comprehensive demos showing all variants
- Playwright Testing - Browser automation testing
- Datastar Documentation - Reactivity framework
- templ Documentation - Go templating engine
- shadcn/ui - Original component library
MIT License - see LICENSE for details.