This repository provides libraries for building applications for Salesforce's Lightning Experience in Go using SLDS-styled masc components, which follow the Elm Architecture.
Thunder is made up of these masc components, a thunder
LWC for running the
compiled wasm applications, a lightweight API designed to mirror the REST
API, and a CLI to make development a joy.
Repository Structure:
. (repo root)
├ cmd/thunder/ CLI source (Cobra commands: serve, deploy)
│ ├ main.go CLI implementation
│ ├ main_test.go CLI command tests
├ salesforce/ embedded metadata templates
│ ├ classes/ Apex classes
│ └ lwc/ LWC wrappers (`go`, `thunder`)
├ components/ MASC components for Thunder apps
├ api/ REST proxy for WASM apps, UI API metadata (GetObjectInfo, GetPicklistValuesByRecordType), Record API for convenient field access (StringValue, Value), Places API for address autocomplete, Settings API for Thunder configuration, and Exit API for application lifecycle management
└ examples/ example Thunder applications
├ thunderDemo/ main demo app showcasing all components
└ validation/ comprehensive form validation example
Key parts:
- thunder LWC (
c:thunder
):- Loads a Go WASM app as static resource, injects global API functions, and runs the app.
- Exposes the
recordId
from Lightning record pages to Go WASM code viaglobalThis.recordId
.
- Thunder SLDS Components (
components/
):- Go library offering SLDS-styled Masc components like
Button
,DataTable
,Grid
,Textarea
, andStencil
.
- Go library offering SLDS-styled Masc components like
- Apex GoBridge (
salesforce/classes/GoBridge.cls
):@AuraEnabled callRest(...)
to map REST API calls to Apex.
- thunder CLI (
cmd/thunder
):- Command-line tool to serve a Thunder app while in development and, and to
build and deploy it to a Salesforce org.
Example Applications (
examples/
): - thunderDemo: Demonstrates all Thunder components in a Go WASM app, organized into Actions, Data, ObjectInfo, and Layout tabs. Showcases component-only development without direct SLDS classes or elem usage.
- validation: Comprehensive form validation example with real-time error handling, demonstrating ValidationState and validated components.
- Command-line tool to serve a Thunder app while in development and, and to
build and deploy it to a Salesforce org.
Example Applications (
Getting Started:
- Install dependencies:
- Go 1.24+ (with WASM support)
- Force CLI
- Run the thunderDemo app locally:
This compiles
$ force login $ thunder serve ./examples/thunderDemo
examples/thunderDemo/main.go
and starts a web server to serve the app. - Deploy to Salesforce using
thunder deploy ./examples/thunderDemo --tab
- Click Fetch Accounts to see a data table rendered from your Thunder app.
Thunder provides a comprehensive set of SLDS-styled components for building Lightning Experience applications:
TextInput
: Single-line text input with label and validation stylingTextarea
: Multi-line text input for longer content (e.g., addresses, descriptions)Select
: Dropdown selection with picklist optionsDatepicker
: Date input with SLDS calendar styling (usestime.Time
values)Timepicker
: Time input with 24-hour format (HH:MM)Checkbox
: Boolean input with proper labelingRadioGroup
: Multiple choice selection with radio buttons in form layoutRadioButtonGroup
: Multiple choice selection with button-style radio controlsValidatedTextInput
,ValidatedTextarea
, etc.: Form components with built-in validation state management
Grid
&GridColumn
: Responsive grid system for flexible layoutscomponents.Grid( components.GridColumn("1-of-2", /* first column content */), components.GridColumn("1-of-2", /* second column content */), components.GridColumn("1-of-1", /* full-width content */), )
CenteredGrid
: Grid with center alignment for loading states and centered contentCard
: Content containers with headers and proper spacingPage
&PageHeader
: Page-level layout with consistent stylingModal
: Dialog overlays for secondary workflowsModalWithClose
: Modal with built-in close button and enhanced functionalityForm
: Semantic form wrapper with proper SLDS stylingContainer
: Basic layout wrapper to avoid direct element usageSpacer
: Flexible spacing container with margin/padding optionsMarginTop
,MarginBottom
,PaddingHorizontal
, etc.: Semantic spacing components
DataTable
: Feature-rich data tables with sorting and actionsDataTableWithMenu
: Enhanced data table with dropdown menu actions and loading statesLookup
: Search and selection for related recordsAddressAutocomplete
: Google Maps Places API integration for address input with autocomplete suggestions
Button
: Action buttons with variant styling (Neutral, Brand, Destructive)LoadingButton
: Button with built-in spinner and disabled stateBadge
: Status indicators and labelsBreadcrumb
: Navigation hierarchy displayIcon
: SLDS icon integrationProgressBar
: Progress indication for long-running operationsSpinner
: Loading indicators in multiple sizesLoadingSpinner
: Centered loading spinner for containersStencil
: Skeleton placeholders for loading statesTabs
: Tabbed content organizationToast
: Notification messages
Text
: Styled text with size variants (Small, Regular, Large)Paragraph
: Paragraph elements with text stylingHeading
: Heading elements (H1/H2/H3) with semantic sizing (Small, Medium, Large)
- Complete Abstraction: No direct SLDS classes or masc elements required in application code
- Semantic APIs: Type-safe spacing, sizing, and styling options
- Consistent Spacing: Semantic spacing components (Spacer, MarginTop, etc.)
- Responsive Design: Grid system adapts to different screen sizes
- Accessibility: Full SLDS accessibility compliance
- Event Handling: Clean event binding with Go functions
- Type Safety: Strongly typed APIs for reliable development
- Loading States: Built-in support for loading spinners and disabled states
Thunder components provide complete abstraction from SLDS classes and DOM elements:
// Instead of using elem.Div with SLDS classes
elem.Div(
masc.Markup(masc.Class("slds-m-top_medium", "slds-align_absolute-center")),
components.Spinner("medium"),
)
// Use semantic layout components
components.MarginTop(components.SpaceMedium,
components.LoadingSpinner("medium"),
)
// Complex layouts with semantic spacing
func (m *AppModel) renderPatientForm(send func(masc.Msg)) masc.ComponentOrHTML {
return components.Page(
components.PageHeader("Patient Information", "Enter patient details"),
components.Card("Patient Details",
components.Grid(
components.GridColumn("1-of-2",
components.ValidatedTextInput("First Name", m.firstName,
components.ValidationState{
Required: true,
HasError: m.hasError("firstName"),
ErrorMessage: m.errors["firstName"],
Placeholder: "Enter first name",
Tooltip: "Legal first name as shown on ID",
},
func(e *masc.Event) {
send(firstNameMsg(e.Target.Get("value").String()))
},
),
),
components.GridColumn("1-of-2",
components.ValidatedTextInput("Last Name", m.lastName,
components.ValidationState{
Required: true,
Placeholder: "Enter last name",
Tooltip: "Legal last name as shown on ID",
},
func(e *masc.Event) {
send(lastNameMsg(e.Target.Get("value").String()))
},
),
),
),
// Loading button with built-in spinner - conditional rendering with masc.If
masc.If(m.isSubmitting,
components.LoadingButton("Saving...", components.VariantBrand),
),
masc.If(!m.isSubmitting,
components.Button("Save", components.VariantBrand, func(e *masc.Event) {
send(saveMsg{})
}),
),
),
)
}
Thunder provides validated form components that handle error states, required field validation, and help text. Each validated component includes:
- Error State Management: Red styling for validation errors
- Required Field Indicators: Asterisk (*) for required fields
- Help Text: Descriptive text below form fields
- Real-time Validation: Immediate feedback on user input
ValidatedTextInput
: Text input with validation stateValidatedTextarea
: Multi-line text with validationValidatedSelect
: Dropdown selection with validationValidatedRadioButtonGroup
: Radio button group with validation stateValidatedDatepicker
: Date input with validation (usestime.Time
values)ValidatedTimepicker
: Time input with validation (24-hour format)ValidatedLookup
: Search and selection component with validation state
All validated components use the ValidationState
struct:
type ValidationState struct {
HasError bool // Show error styling
Required bool // Show asterisk indicator
ErrorMessage string // Error text to display
HelpText string // Help text below field
Tooltip string // Tooltip text (shows as help icon next to label)
Placeholder string // Input placeholder text
}
Validated components support tooltips that appear as help icons (ⓘ) next to field labels:
- Help Icon: Unicode info symbol appears next to labels when tooltip is provided
- Native Tooltip: Uses HTML
title
attribute for cross-browser compatibility - SLDS Styling: Proper spacing and color for SLDS compliance
// Field with tooltip and placeholder
components.ValidatedTextInput("Phone Number", m.phone, components.ValidationState{
Required: false,
Tooltip: "Include area code. Start international numbers with +",
Placeholder: "(555) 123-4567",
HelpText: "Contact phone number (optional)",
}, func(e *masc.Event) {
send(phoneChangedMsg(e.Target.Get("value").String()))
})
Thunder provides convenience functions for common ValidationState configurations:
// Simple tooltip
validation := components.WithTooltip("This field requires special formatting")
// Placeholder only
validation := components.WithPlaceholder("Enter value here")
// Both tooltip and placeholder
validation := components.WithTooltipAndPlaceholder("Help text", "placeholder")
// Required field
validation := components.Required()
// Required field with tooltip
validation := components.RequiredWithTooltip("This required field needs special attention")
validationState := components.ValidationState{
HasError: len(m.email) > 0 && !isValidEmail(m.email),
Required: true,
ErrorMessage: "Please enter a valid email address",
HelpText: "We'll use this to send important updates",
Tooltip: "Email format: [email protected]",
Placeholder: "[email protected]",
}
components.ValidatedTextInput("Email", m.email, validationState, func(e *masc.Event) {
send(emailChangedMsg(e.Target.Get("value").String()))
})
// Gender selection with validation
genderOptions := []components.RadioButtonOption{
{Label: "Female", Value: "Female"},
{Label: "Male", Value: "Male"},
{Label: "Other", Value: "Other"},
{Label: "Unknown", Value: "Unknown"},
}
validationState := components.ValidationState{
Required: true,
HasError: m.genderValidationError,
ErrorMessage: "Please select a gender",
HelpText: "Gender as it appears on legal documents",
}
components.ValidatedRadioButtonGroup("Gender", "patient-gender", genderOptions, m.selectedGender, validationState, func(value string) {
send(genderChangedMsg(value))
})
The examples/
directory contains complete Thunder applications demonstrating different patterns:
The main demonstration app showcasing all Thunder components across four tabs:
- Actions: Buttons, badges, icons, and date pickers
- Data: Interactive data table with filtering, pagination, and controls
- ObjectInfo: Salesforce metadata display using the UI API
- Layout: Grid system demonstration
Features component-only development with no direct SLDS classes or masc elements.
thunder serve ./examples/thunderDemo
Comprehensive form validation example demonstrating:
- Real-time field validation with
ValidationState
- Required field indicators and error messages
- Loading states with
LoadingButton
- Semantic spacing with
MarginTop
andSpacer
- Address autocomplete integration with Google Maps Places API
- Thunder Settings configuration for API keys
Shows how to build robust forms using only Thunder components.
thunder serve ./examples/validation
Both examples are complete Go modules that can be run independently with thunder serve
or deployed with thunder deploy
.
Thunder provides a CLI with two subcommands, serve
and deploy
, for local development and deployment of Go WASM apps on Salesforce.
go install github.com/octoberswimmer/thunder/cmd/thunder@latest
thunder serve [dir] --port PORT # build & serve locally (defaults to current dir)
thunder deploy [dir] [--tab] # deploy app to Salesforce org (defaults to current dir)
--port, -p
: Port to serve on (default8000
)
thunder serve
:
- Builds the app in dev mode (
GOOS=js GOARCH=wasm -tags dev
). - Serves on
http://localhost:PORT
, auto-rebuilds on file changes. - Proxies
/services/...
REST calls to your Salesforce org via CLI auth. - Opens your default browser to the served app URL.
The CLI watches Go source files (.go
, go.mod
, go.sum
) and automatically rebuilds the WASM bundle on changes. Refresh the browser to load the latest build.
API REST requests (via /services/
) are automatically proxied through your active Salesforce CLI session. Be sure to run force login
beforehand.
--tab, -t
: Also include a CustomTab in the deployment and open it for the app--watch, -w
: Watch for file changes and automatically redeploy
thunder deploy
:
- Builds a production WebAssembly bundle.
- Packages metadata (static resource, Apex classes, LWC wrappers, app LWC, and optional CustomTab) in-memory.
- Generates
package.xml
(includes CustomTab if requested) and deploys all metadata via your CLI session. - With
--tab
, adds a CustomTab to the package, deploys it, and opens/lightning/n/<app>
in your browser. - With
--watch
, monitors Go source files and automatically redeploys on changes for rapid development cycles.