Skip to content

HazelnutParadise/syralit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

153 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Syralit

Interactive data apps in Go.

CI Go Reference Go Report Card Go Version License: MIT

Syralit is a Go-native framework for building interactive data apps, dashboards, and AI tool interfaces — inspired by Streamlit, designed for Go.

Write Go functions, get a live web app. No JavaScript, no HTML templates, no frontend build step.

package main

import sy "github.com/HazelnutParadise/syralit"

func main() {
    sy.App(func() {
        sy.Title("Hello Syralit")
        name := sy.TextInput("Your name")
        if name != "" {
            sy.Success("Hello, " + name + "!")
        }
    })
}

Installation

go install github.com/HazelnutParadise/syralit/cmd/syralit@latest

Requirements

  • Go 1.25+

Quick Start

syralit new myapp    # scaffold a new project
cd myapp
syralit dev          # hot reload with state preservation

Or manually:

package main

import sy "github.com/HazelnutParadise/syralit"

func main() {
    sy.App(func() {
        sy.Title("My App")
        if sy.Button("Click me") {
            sy.Balloons()
        }
    })
}
go run .
# Open http://localhost:8600

Agent Skills

The skills/ directory contains Agent Skills for building Syralit apps with AI coding assistants.

Install it into your project with the skills CLI:

npx skills add HazelnutParadise/syralit/skills

Or copy the skills/syralit-dev/ folder into your agent's skills directory manually (e.g. .claude/skills/).

Screenshots

Real screenshots captured from the runnable examples in examples/.

Showcase dashboard (examples/showcase)

Light Dark
Syralit showcase dashboard Syralit showcase dashboard dark mode

Data explorer (examples/data-explorer)

Light Dark
Syralit data explorer analysis view Syralit data explorer analysis view dark mode

Click-to-filter dashboard (examples/insyra-interactive)

Light Dark
Syralit insyra interactive dashboard Syralit insyra interactive dashboard dark mode

Data studio — upload → group → click to drill down (examples/data-studio)

Light Dark
Syralit data studio Syralit data studio dark mode

Conference registration form (examples/form-app)

Light Dark
Syralit conference registration form Syralit conference registration form dark mode

Beyond Streamlit

Things Syralit does that Streamlit can't, by leaning on Go:

// Background jobs — run work in a goroutine; the page stays responsive and the
// server pushes the result when ready (a Streamlit rerun blocks the whole app).
job := sy.Task("report", func() Report { return buildReport() }) // runs once
if job.Running() {
    sy.Spinner("Crunching…")
} else {
    render(job.Result())
}
  • sy.Task[T] — non-blocking background work with auto-push on completion.
  • sy.Shared[T] — app-wide state shared across all sessions; a Set/Update pushes a live update to every connected client (real-time collaboration).
  • sy.ArtifactCanvas — an agent-updatable canvas region rendered from a controlled DSL of reusable Syralit components. Apps opt in to a POST endpoint with bearer-token auth; new specs animate into place for every open session.
  • sy.Fragment(key, fn, sy.RunEvery(d)) — server-driven live refresh.
  • syralit build — compile the whole app (front-end + backend + your public/) into one self-contained executable; no Python, no runtime, no deps.
  • Fully offline / air-gappedsy.SetAssetURL(name, url) repoints any third-party lib (Chart.js, Leaflet, KaTeX, Plotly, …) to a self-hosted copy; drop the files in public/ and syralit build bakes everything into one binary that needs no internet or CDN (also satisfies strict CSP).
  • Automatic SSE fallback — when a WebSocket can't be established (e.g. a proxy that blocks WS upgrades), the client transparently switches to a plain HTTP transport: Server-Sent Events downstream + POST upstream. No code change.
  • sy.Handler(cfg, fn) — mount a Syralit app as a plain http.Handler inside an existing Go server (works behind http.StripPrefix sub-paths).
  • Native desktop apps — ship the exact same app code as a desktop window via integrations/desktop (Wails v3): sydesktop.App(fn) instead of sy.App(fn), and the Go code runs on the user's machine with direct local file access. Packaging a Streamlit app as a desktop binary is famously painful; here it's one import swap.
  • Typed state via generics (sy.State[T]) and typed sy.Task[T] results.

Features

Input Widgets

Widget Returns Description
Button bool Clickable button (true for one rerun)
TextInput string Single-line text
PasswordInput string Masked text input
TextArea string Multi-line text
NumberInput float64 Number with min/max/step
Slider float64 Range slider
RangeSlider (float64, float64) Two-handle slider returning a (low, high) range
DateSlider string Slider over a date range, returns "YYYY-MM-DD"
TimeSlider string Slider over a time range, returns "HH:MM"
SelectSlider string Discrete slider with labels
Checkbox bool Checkbox
Toggle bool Toggle switch
Radio string Radio button group
SelectBox string Dropdown (auto-searchable at 20+ items)
MultiSelect []string Multi-select dropdown
DateInput string Date picker (YYYY-MM-DD)
DatetimeInput string Date+time picker (YYYY-MM-DD HH:MM)
DateRangeInput (string, string) Start/end date pickers
TimeInput string Time picker (HH:MM)
ColorPicker string Color hex picker
FileUploader *UploadedFile File upload
FileUploaderMultiple []*UploadedFile Multi-file upload
CameraInput string Webcam capture
AudioInput string Microphone recording
ChatInput string Chat message input
Feedback string Thumbs up/down
SegmentedControl string Segmented buttons (SegmentedControlMulti[]string)
Pills string Pill-style buttons (PillsMulti[]string)
Pagination int Page selector
MenuButton string Dropdown button returning the clicked option

Plus: DownloadButton, LinkButton, PageLink, Badge.

Display

Title, Header, Subheader, Text, Textf, Markdown, Caption, Code (syntax highlighting via highlight.js), LaTeX (KaTeX), JSON (interactive tree), HTML, Image, ImageFromBytes, Audio, Video, Link, Metric (with delta indicators), Progress, Spinner, WriteStream (token-by-token streaming), Component (custom HTML/JS), IFrame, PDF (embedded viewer), Exception (styled Go error box).

Agent Artifacts

ArtifactCanvas renders a shared, animated canvas from a safe DSL. The DSL is designed for AI agents: it accepts only a curated set of reusable Syralit components, supports JSON Pointer data binding, and never exposes raw HTML, custom JS, iframes, or the internal Node protocol.

mainBoard := sy.NewArtifactStore("main", sy.ArtifactSpec{
    Version: "v1",
    Layout:  sy.ArtifactLayout{Columns: 2, Gap: 14, Padding: 16},
    Data: map[string]any{
        "summary": map[string]any{"revenue": "$42k"},
    },
    Nodes: []sy.ArtifactNode{{
        ID:        "revenue",
        Component: "metric",
        Props:     map[string]any{"label": "Revenue"},
        Bind:      map[string]string{"props.value": "/summary/revenue"},
    }},
})

notesBoard := sy.NewArtifactStore("notes", notesSpec)
auth := sy.StaticAgentKey("local-agent", sy.Secrets("AGENT_KEY"))

// One discoverable endpoint for every explicitly exposed canvas.
sy.HandleArtifactAPI(
    "/api/agent/artifacts",
    auth,
    mainBoard,
    notesBoard,
)

sy.App(func() {
    sy.ArtifactCanvas(mainBoard, sy.Height(520))
    sy.ArtifactCanvas(notesBoard, sy.Height(240))
})

Set SYRALIT_URL to the public URL printed by syralit run or syralit dev. Agents then discover the available canvases and update one by ID. The discovery response also carries a components object (builtin + custom) so an agent can tell whether an opt-in component like insyra is enabled before using it:

curl "$SYRALIT_URL/api/agent/artifacts" \
  -H "Authorization: Bearer $AGENT_KEY"

curl -X POST "$SYRALIT_URL/api/agent/artifacts" \
  -H "Authorization: Bearer $AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"artifact":"main","expected_revision":1,"spec":{"version":"v1","nodes":[{"id":"msg","component":"text","props":{"text":"Updated"}}]}}'

The update response includes a revision plus page/selector preview metadata. Browser-capable agents wait until the selected canvas has the returned data-artifact-revision and data-artifact-state="settled", then capture that element. This waits for the keyed transition, charts, images, and fonts rather than taking a screenshot mid-animation. expected_revision must match the latest discovery/current-spec response; stale writes receive 409 Conflict instead of silently overwriting a newer agent update. In hot-reload mode, the public dev server proxies /api/ to its current child; never use the ephemeral child port shown in internal diagnostics.

HandleArtifactEndpoint remains available when each canvas needs its own route or authenticator. ArtifactAPIHandler and ArtifactHandler return ordinary http.Handler values for mounting the API on another mux or port.

For user-managed keys, implement sy.AgentKeyStore and render sy.AgentKeyManager(store). Syralit provides the UI and callback contract; your app decides whether keys live in memory, a file, a database, or another secret system.

Full DSL reference: docs/artifact-dsl.md

Data

Widget Description
Table Static string table
DataFrame Sortable table; optional row selection (sy.Selectable() → returns selected indices; sy.SelectionMode("single-row") for one), sy.ColumnOrder(...) to reorder/filter columns, typed display via sy.ColConfig
DataEditor Editable table with 11 column types

Column configuration (sy.ColConfig, shared by DataFrame and DataEditor) supports types text, number, checkbox, select, date, time, datetime, link, image, progress, list, plus the display-only mini-chart columns bar_chart / line_chart (cell value is a []float64). Each column may set Format (printf-style, e.g. "$%.2f", "%d%%"), Label (header override), Help (header tooltip), Width, Min/Max/Step, and Color (chart columns). Dynamic row add/delete with sy.DynamicRows().

sy.DataEditor(headers, rows,
    sy.ColConfig(map[string]sy.ColumnConfig{
        "Score": {Type: "number", Min: 0, Max: 100},
        "Pass":  {Type: "checkbox"},
        "Grade": {Type: "select", Options: []string{"A", "B", "C"}},
    }),
    sy.DynamicRows(),
)

Charts

Built-in interactive charts powered by Chart.js:

Chart Input Description
LineChart map[string][]float64 Line chart with multiple series
BarChart map[string][]float64 Bar chart
AreaChart map[string][]float64 Filled line chart
ScatterChart map[string][][2]float64 Scatter plot with xy pairs
PieChart map[string]float64 Pie chart
DoughnutChart map[string]float64 Doughnut chart
HistogramChart []float64, bins Histogram from raw data
RadarChart labels, map[string][]float64 Radar/spider chart
GraphvizChart dot string Graphviz DOT via viz.js

Bar/area/line charts accept sy.Stacked(), sy.Horizontal() (bar), sy.Colors([]string{...}), sy.XLabels(...), and sy.ChartTitle(...).

Line/Bar/Area/Scatter/Pie charts are selectable: add sy.Selectable() and the call returns a *sy.ChartSelection (Series/Index/X/Value) when the user clicks a data point:

if sel := sy.BarChart(data, sy.Selectable(), sy.Key("sales")); sel != nil {
    sy.Textf("%s at %s = %v", sel.Series, sel.X, sel.Value)
}

With sy.RangeSelectable(), dragging across a Line/Bar/Area chart selects an x-axis range (sel.Range, Index..EndIndex, X..EndX) — the building block for time-series drill-downs:

if sel := sy.LineChart(data, sy.RangeSelectable(), sy.Key("ts")); sel != nil && sel.Range {
    sy.Textf("from %s to %s", sel.X, sel.EndX)
}

External charting library integrations (CDN-loaded, accepting JSON specs):

Chart Library Streamlit Equivalent
VegaLiteChart Vega-Lite / vega-embed st.altair_chart
PlotlyChart Plotly.js st.plotly_chart
PyplotChart SVG/PNG images st.pyplot
BokehChart BokehJS st.bokeh_chart
PydeckChart deck.gl st.pydeck_chart

Layout

// Columns (equal or weighted)
cols := sy.Columns(3)
cols[0](func() { sy.Text("Col 1") })

cols := sy.WeightedColumns(2, 1, 1)

// Tabs
tab := sy.Tabs([]string{"Tab1", "Tab2"})
tab("Tab1", func() { sy.Text("Content 1") })

// Other containers
sy.Sidebar(func() { ... })
sy.Expander("Title", func() { ... })
sy.Container(func() { ... }, sy.Border())
sy.Form("key", func() { ... }, sy.ClearOnSubmit())  // ClearOnSubmit optional
sy.Status("Loading", "running", func() { ... })
sy.Fragment("key", func() { ... })  // partial rerun
sy.Space(sy.Height(32))             // vertical whitespace
sy.Bottom(func() { ... })           // pin content to the bottom of the viewport

State & Session

// Typed state (persists across reruns)
count := sy.State("count", 0)
count.Get()
count.Set(42)

// Query parameters (SetQueryParam updates the browser URL — shareable state)
val := sy.QueryParam("page")
sy.SetQueryParam("page", "2")

// Reset a widget to its default (e.g. clear a chart selection)
sy.ResetWidget("key")

// Request context (headers, cookies, host, IP, locale) — st.context
ctx := sy.Context()
lang := ctx.Locale

// Flow control
sy.Stop()   // halt rendering
sy.Rerun()  // force rerun

Multi-Page Apps

func init() {
    sy.AddPage("Home", homePage, sy.PageIcon("🏠"), sy.PageOrder(1))
    sy.AddPage("About", aboutPage, sy.PageIcon("ℹ️"), sy.PageOrder(2))
}

func main() { sy.App(nil) }

Auth

// Login gate blocks rendering until authenticated
username := sy.LoginGate(func(user, pass string) bool {
    return user == "admin" && pass == "secret"
})

// Role-based access
user := sy.User()  // map[string]string or nil
sy.Login(map[string]string{"name": "admin", "role": "admin"})
sy.Logout()

OIDC single sign-on lives in a separate module (integrations/oidc) so its dependency tree never touches the core. Wrap the app handler and every visitor signs in through Google / Microsoft Entra / Keycloak / Auth0 / any OIDC provider; sy.User() then returns the verified claims:

import syoidc "github.com/HazelnutParadise/syralit/integrations/oidc"

handler, _ := syoidc.Protect(sy.Handler(sy.Config{}, app), syoidc.Config{
    Issuer:       "https://accounts.google.com",
    ClientID:     os.Getenv("OIDC_CLIENT_ID"),
    ClientSecret: os.Getenv("OIDC_CLIENT_SECRET"),
    RedirectURL:  "http://localhost:8600/auth/callback",
    CookieSecret: []byte(os.Getenv("COOKIE_SECRET")),
})
http.ListenAndServe(":8600", handler)

Caching

data := sy.CacheData("key", func() []Row {
    return fetchFromDB()
}, sy.TTL(5 * time.Minute))

db := sy.CacheResource("db", func() *sql.DB {
    return openDB()
})

Feedback & Notifications

sy.Success("Done!")
sy.Error("Failed!")
sy.Warning("Watch out")
sy.Info("Note")
sy.Toast("Message", "success")
sy.Balloons()
sy.Snow()
sy.Dialog("Settings", func() { ... })

Streaming (LLM Output)

sy.WriteStream(func(yield func(string)) {
    for _, word := range words {
        yield(word + " ")
        time.Sleep(30 * time.Millisecond)
    }
})

Chat UI

msgs := sy.State("msgs", []map[string]string{})
for _, m := range msgs.Get() {
    sy.ChatMessage(m["role"], func() {
        sy.Markdown(m["content"])
    })
}
if input := sy.ChatInput("Ask something..."); input != "" {
    msgs.Set(append(msgs.Get(), map[string]string{
        "role": "user", "content": input,
    }))
}

Maps

sy.Map([]sy.MapPoint{
    {Lat: 25.0330, Lon: 121.5654, Text: "Taipei 101"},
}, sy.Height(450))

Database

db := sy.Connection("mydb")
rows := sy.SQLQuery(db, "SELECT * FROM users")

Common Options

sy.Key("unique_key")        // stable widget identity
sy.DefaultValue(val)         // initial value
sy.Placeholder("hint")      // placeholder text
sy.Help("tooltip")          // help tooltip
sy.Disabled()               // disable widget
sy.Min(0), sy.Max(100)      // numeric range
sy.Step(0.5)                // numeric step
sy.Height(300), sy.Width(400)
sy.ChartTitle("Title")      // chart title
sy.Border()                 // container border
sy.Color("green")           // element color
sy.Language("go")           // code language

// Button styling (Button, LinkButton, DownloadButton)
sy.Icon("🚀")               // prefix a button label with an icon
sy.ButtonType("secondary")  // "primary" (default), "secondary", "tertiary"
sy.UseContainerWidth()      // make a button span its container

sy.Border()                 // also: bordered Metric card
sy.MinDate("2026-01-01")    // DateInput / DateRangeInput lower bound
sy.MaxDate("2026-12-31")    // DateInput / DateRangeInput upper bound

Insyra Integration

Syralit has first-class support for Insyra DataTable and DataList via a cleanly separated adapter package. The core framework never imports Insyra.

import syi "github.com/HazelnutParadise/syralit/integrations/insyra"

// DataTable (multi-column)
syi.Table(dt)                           // render DataTable
syi.Preview(dt, 5)                      // first N rows
syi.EditableTable(dt, sy.Key("edit"))   // editable DataTable
col := syi.ColumnSelect("Column", dt)   // column picker
syi.Metrics(dt, col)                    // count, mean, min, max
syi.BarChart(dt, "Category", "Value")   // chart from columns ("Category" = axis labels)
syi.LineChart(dt, "Month", "Revenue")   // all chart helpers accept sy.Option and return
syi.ScatterChart(dt, "X", "Y")          // *sy.ChartSelection when sy.Selectable() is set
syi.MultiLineChart(dt, "Month", nil)    // every numeric column as a series (st.line_chart(df))

// Click-to-filter dashboards: GroupBy chart + selection + filter
sel := syi.GroupedBarChart(dt, "Region", "Revenue", insyra.OpSum,
    sy.Selectable(), sy.Key("by_region"))
syi.Table(syi.FilterBySelection(dt, "Region", sel))  // nil selection = unfiltered
sub := syi.FilterEquals(dt, "Region", "North")       // pure data helper
edited := syi.EditableDataTable(dt, sy.Key("e"))     // edits → new *insyra.DataTable
syi.DownloadCSV("Export", dt, "data.csv")            // CSV download button
syi.RollingMeanChart(dt, "Month", "Revenue", 7)      // raw + rolling mean overlay
syi.CumSumChart(dt, "Month", "Revenue")              // cumulative sum
syi.PctChangeChart(dt, "Month", "Revenue", 1)        // percent change bars
out := syi.AddFormulaColumn(dt, "ccl")               // interactive CCL formula column

// DataList (single series) — the symmetric counterpart
syi.List(dl)                            // single-column table
syi.ListPreview(dl, 5)                  // first N values
syi.EditableList(dl, sy.Key("edl"))     // editable single column → []any
syi.ListMetrics(dl)                     // count, mean, min, max
syi.ListDescribe(dl)                    // count/mean/std/min/25%/50%/75%/max
syi.ListBarChart(dl)                    // value over index
syi.ListLineChart(dl)
syi.ListAreaChart(dl)
syi.Histogram(dl, 20)                   // distribution (list-only)

// Statistical analysis (insyra/stats), rendered in the UI
syi.Describe(dt)                              // full per-column summary table
syi.Correlation(dt, "X", "Y", "pearson")     // r + p as metrics
syi.CorrelationMatrix(dt, "pearson")         // pairwise correlation matrix
syi.LinearRegression(dt, "Y", "X1", "X2")    // R²/coeffs table + scatter
syi.TTest(dt, "A", "B", false)               // two-sample t-test

// Load a file into a DataTable (CSV / Excel / JSON)
dt := syi.UploadTable("Upload data")         // file uploader → *DataTable
dt, err := syi.ParseTable(name, bytes)       // parse bytes from any source

// Interactive transforms (non-destructive)
out := syi.FilterBuilder(dt)                 // column/op/value row filter
out := syi.CCLBuilder(dt)                    // add a computed column (CCL)

Native interactive charts beyond the built-in Chart.js layer (Sankey, word cloud, K-line, gauge, funnel, …) live in a separate opt-in subpackage, because they pull in go-echarts and (transitively) chromedp:

import syiplot "github.com/HazelnutParadise/syralit/integrations/insyra/eplot"
import "github.com/HazelnutParadise/insyra/plot"

syiplot.WordCloud(dl, "Tags")                       // no Chart.js equivalent
syiplot.EChart(plot.CreateSankeyChart(cfg, links...)) // any insyra/plot chart

syiplot.SetOffline(true) // inline echarts JS into each chart — no CDN, runs
                         // air-gapped / under strict CSP / as a `syralit build` binary

Insyra DSL — dynamic computation

Run the Insyra CLI DSL (.isr) from Go and render the result, or let agents embed a DSL script directly in an Artifact for live server-side computation. This is a separate opt-in subpackage because the DSL engine pulls in the full Insyra CLI dependency tree (cobra, database drivers, parquet/arrow, readline):

import syidsl "github.com/HazelnutParadise/syralit/integrations/insyra/insyradsl"

// Go widget: run a script (safe mode) and auto-render. Cached by script hash.
syidsl.DSL(`
newdl Q1 Q2 Q3 Q4 as quarter
newdl 42 55 61 78 as revenue
newdt quarter revenue as t
setcolnames t quarter revenue
`, syidsl.Render("bar_chart"), syidsl.Output("t"), syidsl.X("quarter"), syidsl.Y("revenue"))

res := syidsl.RunDSL(script) // low-level: inspect res.Vars / res.Output / res.Err

RunDSL runs in safe mode by default — a default-deny allowlist of pure, in-memory compute commands; load/save/db/fetch/run are rejected, and each run uses an isolated, ephemeral environment (no shared ~/.insyra state). Pass syidsl.Unrestricted() only for trusted, app-authored scripts.

Importing the package also registers an insyra Artifact component, so an agent can POST a spec that computes live instead of only binding static data:

{ID: "chart", Component: "insyra", Props: map[string]any{
    "script": "newdl North South West as region\nnewdl 12 18 9 as deals\n" +
        "newdt region deals as t\nsetcolnames t region deals",
    "render": "bar_chart", "output": "t", "x": "region", "y": "deals"}}

Desktop Apps

integrations/desktop ships a Syralit app as a native desktop window (Wails v3). It is a separate Go module — webapps never pull the Wails dependency tree:

import (
    sy "github.com/HazelnutParadise/syralit"
    sydesktop "github.com/HazelnutParadise/syralit/integrations/desktop"
)

func main() {
    sydesktop.App(func() {
        sy.Title("My tool")
        // ... exactly the same code as a webapp
    }, sydesktop.WindowSize(1200, 800), sydesktop.MinSize(640, 480))
}

The app serves on a loopback-only random port, the window renders it through the OS webview, and closing the window shuts everything down. Options: WindowTitle, WindowSize, MinSize, Frameless, Icon(pngBytes), Config(sy.Config), AllowBrowser(). sydesktop.Run is the error-returning variant. Because the Go process runs on the user's machine, local files are directly readable — no upload round-trip (see examples/desktop-demo).

By default the server is locked to its window: requests without the window's per-launch token get 403, so other local browsers can't open the app (pass AllowBrowser() to permit it). /api/ endpoints are exempt — agent artifact endpoints keep working with their own bearer auth. The app's environment gets SYRALIT_URL so agent subprocesses it spawns can find those endpoints, and an explicit Config(sy.Config{Port: N}) pins the port when external agents need a stable address.

Build requirements (Wails v3's): nothing extra on Windows (WebView2 ships with Windows 10/11), Xcode command-line tools on macOS, webkit2gtk on Linux. For icons/installers use the wails3 CLI packaging tooling.

Hot reload: syralit dev works for desktop apps too — the native window attaches to the dev supervisor and survives rebuilds like a browser tab would (session state preserved, build errors overlay in the window). Closing the window leaves the dev session running; it auto-closes when the supervisor stops.

Configuration

syralit.toml

title = "My App"
host = "0.0.0.0"
port = 8600

[theme]
mode = "system"        # "light" | "dark" | "system"
accent = "#7C3AED"
radius = "12px"
button_radius = "999px"                 # defaults to radius
background_color = "#ffffff"
secondary_background_color = "#f8f9fb"  # widget/code/sidebar surface
text_color = "#1f2329"
link_color = "#2563eb"                  # defaults to accent
link_underline = true                   # true: always, false: never, unset: on hover
code_text_color = "#0f766e"
code_background_color = "#f1f5f9"
border_color = "#e5e7eb"
dataframe_border_color = "#e5e7eb"
dataframe_header_background_color = "#f3f4f6"
show_widget_border = true
show_sidebar_border = true
# Basic palette (badges, alerts, status colors); each of red/orange/yellow/
# blue/green/violet/gray also has <name>_background_color and <name>_text_color.
red_color = "#dc2626"
blue_background_color = "#eff6ff"
# Chart palettes (categorical drives built-in chart series colors).
chart_categorical_colors = ["#7c3aed", "#2563eb", "#16a34a"]
chart_sequential_colors = ["#f0fdfa", "#0f766e"]
chart_diverging_colors = ["#dc2626", "#f8fafc", "#2563eb"]
# Fonts: "sans-serif" | "serif" | "monospace" select the built-in Source Sans 3 /
# Source Serif 4 / Source Code Pro (embedded, served locally — no CDN); any
# other value is used as a CSS font-family list.
font = "sans-serif"
heading_font = "serif"
code_font = "monospace"
base_font_size = 16
base_font_weight = 400
heading_font_sizes = ["2rem", "1.5rem", "1.15rem"]  # h1..h6
heading_font_weights = [700, 650, 600]
code_font_size = "0.875rem"
code_font_weight = 400

[[theme.font_faces]]   # load custom fonts (otf/ttf/woff/woff2)
family = "Inter"
url = "/fonts/inter.woff2"   # public/ path or absolute URL
weight = "100 900"
style = "normal"

[theme.sidebar]        # sidebar-only overrides — every color/font/radius key
font = "sans-serif"    # above works here too (inherits main theme when unset)
accent = "#f59e0b"

[secrets]
api_key = "sk-..."
db_dsn = "postgres://..."

[server]
max_upload_size_mb = 50   # FileUploader/CameraInput cap (default 10 MB)
ssl_cert_file = "cert.pem"  # serve HTTPS when both are set
ssl_key_file = "key.pem"

[i18n]                    # localize built-in UI text
connecting = "連線中…"
loading = "載入中…"

Runtime Configuration

sy.SetPageConfig(
    sy.PageTitle("My App"),
    sy.PageLayout("wide"),
    sy.ConfigIcon("🚀"),
    sy.PrimaryColor("#ff4b4b"),
    sy.InitialSidebarState("collapsed"),
    sy.ConfigMenuItems("https://…/help", "https://…/issues", "**About** markdown"),
)

apiKey := sy.Secrets("api_key")

CLI Commands

Command Description
syralit new <name> Scaffold a new project in a new folder
syralit new . Scaffold into the current directory (no wrapper folder)
syralit dev Hot reload with state preservation
syralit run Build and run once (no watching)
syralit build [-o out] [dir] Compile to a single self-contained executable

Static Files & Bundling

Drop files in a public/ directory and they're served at the site root — public/logo.png/logo.png. In syralit dev they're served from disk; for production, syralit build folds public/ (and any assets/ overrides) into the binary via //go:embed, so the result is one executable with the front-end, backend, and all your static files — nothing to copy alongside it.

syralit build              # → ./<dir-name>[.exe], everything embedded
syralit build -o myapp .   # custom output path

You can also wire static files manually with sy.Static(fsys) (served at the root) and sy.StaticAssets(fsys) (overrides the built-in front-end assets).

Testing

sy.AppTest is a headless testing harness (the Go counterpart of Streamlit's st.testing.v1.AppTest): render without a server, simulate widget input and button clicks, and assert on the resulting tree.

at := sy.NewAppTest(func() {
    name := sy.TextInput("Name", sy.Key("name"))
    if sy.Button("Greet", sy.Key("greet")) {
        sy.Success("Hello, " + name)
    }
})
at.Run()
at.SetValue("name", "Ada")
at.Click("greet") // or at.ClickLabel("Greet")
at.Run()
// at.Texts("status") -> ["Hello, Ada"]

For a single render, sy.RenderOnce(appFn) *Node returns the UI tree directly:

tree := sy.RenderOnce(func() { sy.Metric("Users", "24,891") })
if len(tree.Find("metric")) != 1 { t.Fatal("expected a metric") }

The repo also ships a browser-level UI suite under uitest/ — a separate Go module (so its chromedp dependency never touches the framework's go.mod) that drives headless Chrome against an in-process app and asserts on real rendered behavior: widget round-trips over WebSocket, CSS visibility, canvas chart clicks, multi-file uploads, and sub-path mounting.

cd uitest && go test ./...   # requires a local Chrome/Chromium

The desktop module has its own e2e suite (Windows) that opens real native windows: production launch + browser lockdown + agent artifact auth + graceful close, and syralit dev hot reload with the window surviving rebuilds. It is gated so plain test runs stay headless:

cd integrations/desktop
SYRALIT_DESKTOP_E2E=1 go test ./...   # opens real windows briefly

Examples

The examples/ directory contains runnable demo apps:

Example Description
hello Minimal single-page app with basic widgets
showcase Comprehensive 6-page demo of all features
chatbot Chat UI with simulated streaming AI responses
form-app Conference registration form with validation
data-explorer 3-page sales dashboard with charts, filters, and data editing
auth-demo Authentication with LoginGate and role-based access control
agent-artifact Artifact Canvas studio with multiple live boards, preset scenarios, local compose controls, and agent endpoints
mega-demo 10-page app showcasing every feature: all widgets, charts, layout, forms, data tables, chat, maps, state
insyra-demo Insyra integration: tables, stats, transforms, file upload, native charts
insyra-charts Native go-echarts charts (Sankey/gauge/funnel/word cloud) with offline inlining
insyra-artifact Insyra DSL dynamic computation: syidsl.DSL widget and the agent-driveable insyra Artifact component
embed-scroll Themed scrollbars inside embedded Component iframes (follow light/dark)
theme-fonts Theming: built-in Source fonts, custom @font-face, palette/link/button-radius/chart colors, sidebar overrides
streamlit-parity PDF viewer, multi-file upload, collapsed sidebar, app menu, free-entry MultiSelect, clipped media
insyra-interactive Click-to-filter dashboard: selectable GroupBy chart drives an Insyra-filtered table, metrics, and deep-linkable URL state
data-studio Full upload → explore workflow: file upload, column pickers, aggregate switch, selectable GroupBy chart, drill-down detail and statistics
oidc-login OIDC single sign-on via integrations/oidc (standalone module)
desktop-demo Native desktop window via integrations/desktop (standalone module): same app code, direct local file access

Run any example:

cd examples/chatbot
go run .

Streamlit parity

Syralit covers the commonly-used Streamlit surface in idiomatic Go. See docs/STREAMLIT_PARITY.md for the full mapping and the few intentional gaps.

Changelog

See CHANGELOG.md.

License

MIT

About

A Go-native, Streamlit-inspired framework for data apps, dashboards, and AI tools.

Topics

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors