widgets
Package widgets provides UI components for building widget trees.
This package contains the concrete widget implementations that developers use to build user interfaces, including layout widgets (Row, Column, Stack), display widgets (Text, Icon, Image), input widgets (Button, TextField, Checkbox), and container widgets (Container, Padding, SizedBox).
Widget Construction
Drift widgets use a two-tier construction pattern:
Tier 1: Struct Literal \(canonical, full control\)
btn := Button{
Label: "Submit",
OnTap: handleSubmit,
Color: colors.Primary,
Disabled: !isValid,
Haptic: true,
}
This is the PRIMARY way to create widgets. All fields are accessible. For themed styling, use theme.XxxOf constructors from pkg/theme instead.
Tier 2: Layout Helpers \(ergonomics for layout widgets\)
Convenience helpers for common layout patterns:
StackOf, VSpace, HSpace, Centered.
WithX Chaining \(for themed widgets\)
WithX methods on widgets allow overriding themed defaults:
btn := theme.ButtonOf(ctx, "Submit", handleSubmit).
WithBorderRadius(0). // Sharp corners
WithPadding(layout.EdgeInsetsAll(20))
WithX methods return COPIES; they never mutate the receiver.
API Rules
- Canonical = struct literal. Always works, always documented.
- Layout helpers (StackOf, VSpace, HSpace, Centered) exist for ergonomics.
- For themed widgets, use theme.XxxOf constructors from pkg/theme.
- WithX returns copies. Doc comment must state "returns a copy".
Layout Widgets
Use Row and Column struct literals for horizontal and vertical layouts:
widgets.Row{Children: []core.Widget{...}}
widgets.Column{Children: []core.Widget{...}}
Input Widgets
Button, TextField, Checkbox, Radio, and Switch handle user input. Use struct literals for explicit control or theme.XxxOf for themed widgets:
// Struct literal (explicit)
widgets.Button{Label: "Submit", OnTap: onTap, Color: colors.Primary, Haptic: true}
// Themed constructor (from pkg/theme)
theme.ButtonOf(ctx, "Submit", onTap)
Scrolling
ScrollView provides scrollable content with customizable physics:
widgets.ScrollView{Child: content, Physics: widgets.BouncingScrollPhysics{}}
Style Guide for Widget Authors
When adding WithX methods:
- Use value receiver, not pointer
- Return the modified copy, never mutate
- Doc comment: "returns a copy of X with..."
Constants
const (
// ActivityIndicatorSizeMedium is a medium spinner.
ActivityIndicatorSizeMedium = platform.ActivityIndicatorSizeMedium
// ActivityIndicatorSizeSmall is a small spinner.
ActivityIndicatorSizeSmall = platform.ActivityIndicatorSizeSmall
// ActivityIndicatorSizeLarge is a large spinner.
ActivityIndicatorSizeLarge = platform.ActivityIndicatorSizeLarge
)
Variables
var (
SnapThird = SnapPoint{FractionalHeight: 0.33, Name: "third"}
SnapHalf = SnapPoint{FractionalHeight: 0.5, Name: "half"}
SnapFull = SnapPoint{FractionalHeight: 1.0, Name: "full"}
)
DefaultSnapPoints is used when no snap points are provided.
var DefaultSnapPoints = []SnapPoint{SnapFull}
func Clamp
func Clamp(value, min, max float64) float64
Clamp constrains a value between min and max bounds.
func DeviceScaleOf
func DeviceScaleOf(ctx core.BuildContext) float64
DeviceScaleOf returns the current device scale, defaulting to 1 if not found.
func HandleDropdownPointerDown
func HandleDropdownPointerDown(entries []layout.RenderObject)
HandleDropdownPointerDown dismisses open dropdowns on outside taps.
func HasActiveBallistics
func HasActiveBallistics() bool
HasActiveBallistics returns true if any scroll simulations are running.
func RegisterRestartAppFn
func RegisterRestartAppFn(fn func())
RegisterRestartAppFn registers the function to restart the app. This is called by the engine package during initialization.
func SafeAreaBottomOf
func SafeAreaBottomOf(ctx core.BuildContext) float64
SafeAreaBottomOf returns only the bottom safe area inset. Widgets calling this will only rebuild when the bottom inset changes.
func SafeAreaLeftOf
func SafeAreaLeftOf(ctx core.BuildContext) float64
SafeAreaLeftOf returns only the left safe area inset. Widgets calling this will only rebuild when the left inset changes.
func SafeAreaOf
func SafeAreaOf(ctx core.BuildContext) layout.EdgeInsets
SafeAreaOf returns the current safe area insets from context. Widgets calling this will rebuild when any inset changes.
func SafeAreaPadding
func SafeAreaPadding(ctx core.BuildContext) layout.EdgeInsets
SafeAreaPadding returns the safe area insets as EdgeInsets for use with ScrollView.Padding or other widgets. The returned EdgeInsets can be modified using chainable methods:
ScrollView{
Padding: widgets.SafeAreaPadding(ctx), // just safe area
Child: ...,
}
ScrollView{
Padding: widgets.SafeAreaPadding(ctx).Add(24), // safe area + 24px all sides
Child: ...,
}
ScrollView{
Padding: widgets.SafeAreaPadding(ctx).OnlyTop().Add(24), // only top safe area + 24px
Child: ...,
}
func SafeAreaRightOf
func SafeAreaRightOf(ctx core.BuildContext) float64
SafeAreaRightOf returns only the right safe area inset. Widgets calling this will only rebuild when the right inset changes.
func SafeAreaTopOf
func SafeAreaTopOf(ctx core.BuildContext) float64
SafeAreaTopOf returns only the top safe area inset. Widgets calling this will only rebuild when the top inset changes.
func StepBallistics
func StepBallistics()
StepBallistics advances any active scroll simulations.
func ValidateInitialSnap
func ValidateInitialSnap(index int, points []SnapPoint) int
ValidateInitialSnap ensures the initial snap index is valid.
type ActivityIndicator
ActivityIndicator displays a native platform spinner. Uses UIActivityIndicatorView on iOS and ProgressBar on Android.
type ActivityIndicator struct {
core.StatefulBase
// Animating controls whether the indicator is spinning.
// Defaults to true.
Animating bool
// Size is the indicator size (Small, Medium, Large).
// Defaults to Medium.
Size ActivityIndicatorSize
// Color is the spinner color (optional, uses system default if not set).
Color graphics.Color
}
func (ActivityIndicator) CreateState
func (a ActivityIndicator) CreateState() core.State
type ActivityIndicatorSize
ActivityIndicatorSize represents the size of the activity indicator.
type ActivityIndicatorSize = platform.ActivityIndicatorSize
type Align
Align positions its child within itself according to the given alignment.
Align expands to fill available space, then positions the child within that space according to the Alignment field. The child is given loose constraints, allowing it to size itself.
Example:
Align{
Alignment: layout.AlignmentBottomRight,
Child: Text{Content: "Bottom right"},
}
See also:
- Center for centering (equivalent to Align with AlignmentCenter)
- Container for combined alignment, padding, and decoration
type Align struct {
core.RenderObjectBase
Child core.Widget
Alignment layout.Alignment
}
func (Align) ChildWidget
func (a Align) ChildWidget() core.Widget
func (Align) CreateRenderObject
func (a Align) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
func (Align) UpdateRenderObject
func (a Align) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
type AnimatedContainer
AnimatedContainer is a Container that animates changes to its properties.
When properties like Color, Width, Height, Padding, or Alignment change, the widget automatically animates from the old value to the new value over the specified Duration using the specified Curve.
Note: Gradient is not animated; changes to Gradient will apply immediately.
Example:
widgets.AnimatedContainer{
Duration: 300 * time.Millisecond,
Curve: animation.EaseInOut,
Color: s.isActive ? colors.Primary : colors.Surface,
Width: 100,
Height: 100,
Child: child,
}
type AnimatedContainer struct {
core.StatefulBase
// Duration is the length of the animation.
Duration time.Duration
// Curve transforms the animation progress. If nil, uses linear interpolation.
Curve func(float64) float64
// OnEnd is called when the animation completes.
OnEnd func()
// Container properties that will be animated when they change.
Padding layout.EdgeInsets
Width float64
Height float64
Color graphics.Color
Gradient *graphics.Gradient
Alignment layout.Alignment
Child core.Widget
}
Example:
This example shows implicit animation with AnimatedContainer.
package main
import (
"time"
"github.com/go-drift/drift/pkg/animation"
"github.com/go-drift/drift/pkg/graphics"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
var isExpanded bool
// Properties animate automatically when they change
container := widgets.AnimatedContainer{
Duration: 300 * time.Millisecond,
Curve: animation.EaseInOut,
Width: func() float64 {
if isExpanded {
return 200
} else {
return 100
}
}(),
Height: func() float64 {
if isExpanded {
return 200
} else {
return 100
}
}(),
Color: func() graphics.Color {
if isExpanded {
return graphics.RGB(100, 149, 237)
} else {
return graphics.RGB(200, 200, 200)
}
}(),
Child: widgets.Center{Child: widgets.Text{Content: "Tap to toggle"}},
}
_ = container
}
func (AnimatedContainer) CreateState
func (a AnimatedContainer) CreateState() core.State
type AnimatedOpacity
AnimatedOpacity animates changes to opacity over a duration.
When the Opacity property changes, the widget automatically animates from the old value to the new value over the specified Duration.
Example:
widgets.AnimatedOpacity{
Duration: 200 * time.Millisecond,
Curve: animation.EaseOut,
Opacity: s.isVisible ? 1.0 : 0.0,
Child: child,
}
type AnimatedOpacity struct {
core.StatefulBase
// Duration is the length of the animation.
Duration time.Duration
// Curve transforms the animation progress. If nil, uses linear interpolation.
Curve func(float64) float64
// OnEnd is called when the animation completes.
OnEnd func()
// Opacity is the target opacity (0.0 to 1.0).
Opacity float64
// Child is the widget to which opacity is applied.
Child core.Widget
}
Example:
This example shows fade animation with AnimatedOpacity.
package main
import (
"time"
"github.com/go-drift/drift/pkg/animation"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
var isVisible bool
opacity := widgets.AnimatedOpacity{
Duration: 200 * time.Millisecond,
Curve: animation.EaseOut,
Opacity: func() float64 {
if isVisible {
return 1.0
} else {
return 0.0
}
}(),
Child: widgets.Text{Content: "Fading content"},
}
_ = opacity
}
func (AnimatedOpacity) CreateState
func (a AnimatedOpacity) CreateState() core.State
type Axis
Axis represents the layout direction. AxisVertical is the zero value, making it the default for ScrollDirection fields.
type Axis int
const (
AxisVertical Axis = iota
AxisHorizontal
)
func (Axis) String
func (a Axis) String() string
String returns a human-readable representation of the axis.
type BackdropFilter
BackdropFilter applies a blur effect to content behind this widget. The blur is applied within the widget's bounds and affects any content drawn before this widget in the compositing order.
type BackdropFilter struct {
core.RenderObjectBase
Child core.Widget
SigmaX float64
SigmaY float64
}
func NewBackdropFilter
func NewBackdropFilter(sigma float64, child core.Widget) BackdropFilter
NewBackdropFilter creates a BackdropFilter with uniform blur in both directions.
func (BackdropFilter) ChildWidget
func (b BackdropFilter) ChildWidget() core.Widget
func (BackdropFilter) CreateRenderObject
func (b BackdropFilter) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
func (BackdropFilter) UpdateRenderObject
func (b BackdropFilter) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
type BottomSheet
BottomSheet is the widget for rendering a bottom sheet with animations and gestures. Use BottomSheetController to programmatically dismiss or snap the sheet.
type BottomSheet struct {
core.StatefulBase
// Builder creates the sheet content.
Builder func(ctx core.BuildContext) core.Widget
// Controller allows programmatic control of the sheet.
Controller *BottomSheetController
// SnapPoints defines the snap positions as fractions of available height.
// When empty, the sheet sizes to content height.
SnapPoints []SnapPoint
// InitialSnap is the index into SnapPoints to open at.
InitialSnap int
// EnableDrag toggles drag gestures for the sheet.
EnableDrag bool
// DragMode defines how drag gestures interact with content.
DragMode DragMode
// ShowHandle displays the drag handle at the top of the sheet.
ShowHandle bool
// UseSafeArea applies safe area insets to the sheet layout.
UseSafeArea bool
// Theme configures the sheet's visual styling.
Theme BottomSheetTheme
// SnapBehavior customizes snapping and dismiss thresholds.
SnapBehavior SnapBehavior
// OnDismiss fires after the sheet finishes its dismiss animation.
OnDismiss func(result any)
}
func (BottomSheet) CreateState
func (b BottomSheet) CreateState() core.State
type BottomSheetController
BottomSheetController controls a bottom sheet's behavior. Create with NewBottomSheetController and pass to BottomSheet widget. Content inside the sheet can access the controller via BottomSheetScope.Of(ctx).
type BottomSheetController struct {
// contains filtered or unexported fields
}
func NewBottomSheetController
func NewBottomSheetController() *BottomSheetController
NewBottomSheetController creates a new controller for a bottom sheet.
func (*BottomSheetController) AddProgressListener
func (c *BottomSheetController) AddProgressListener(listener func(float64)) func()
AddProgressListener registers a callback for progress changes.
func (*BottomSheetController) Close
func (c *BottomSheetController) Close(result any)
Close triggers the sheet's dismiss animation with the given result. The result will be passed to OnDismiss when animation completes. Safe to call multiple times - only the first call has effect.
func (*BottomSheetController) Extent
func (c *BottomSheetController) Extent() float64
Extent returns the current sheet extent in pixels.
func (*BottomSheetController) Progress
func (c *BottomSheetController) Progress() float64
Progress returns the current open progress from 0.0 to 1.0.
func (*BottomSheetController) SnapTo
func (c *BottomSheetController) SnapTo(index int)
SnapTo animates the sheet to the snap point at the given index.
func (*BottomSheetController) SnapToFraction
func (c *BottomSheetController) SnapToFraction(fraction float64)
SnapToFraction animates the sheet to a fractional snap point. The value is clamped to [0, 1].
type BottomSheetScope
BottomSheetScope provides access to the bottom sheet controller from within sheet content. Use BottomSheetScope.Of(ctx).Close(result) to dismiss the sheet with animation.
type BottomSheetScope struct{}
func (BottomSheetScope) Of
func (BottomSheetScope) Of(ctx core.BuildContext) *BottomSheetController
Of returns the BottomSheetController for the enclosing bottom sheet. Returns nil if not inside a bottom sheet or if the sheet has no controller.
type BottomSheetScrollable
BottomSheetScrollable bridges a scroll controller to the enclosing bottom sheet so the sheet can coordinate content-aware dragging.
Example:
return widgets.BottomSheetScrollable{
Builder: func(controller *widgets.ScrollController) core.Widget {
return widgets.ListView{
Controller: controller,
Children: items,
}
},
}
type BottomSheetScrollable struct {
core.StatefulBase
Controller *ScrollController
Builder func(controller *ScrollController) core.Widget
}
func (BottomSheetScrollable) CreateState
func (b BottomSheetScrollable) CreateState() core.State
type BottomSheetTheme
BottomSheetTheme holds theming data for a bottom sheet. This is passed from the route which has access to the theme system.
type BottomSheetTheme struct {
BackgroundColor graphics.Color
HandleColor graphics.Color
BorderRadius float64
HandleWidth float64
HandleHeight float64
HandleTopPadding float64
HandleBottomPadding float64
}
func DefaultBottomSheetTheme
func DefaultBottomSheetTheme() BottomSheetTheme
DefaultBottomSheetTheme returns default theme values.
type BouncingScrollPhysics
BouncingScrollPhysics adds resistance near edges (iOS style). AllowsOverscroll returns true, enabling spring-back animation.
type BouncingScrollPhysics struct{}
func (BouncingScrollPhysics) AllowsOverscroll
func (BouncingScrollPhysics) AllowsOverscroll() bool
AllowsOverscroll returns true; bouncing physics allow scrolling past boundaries.
func (BouncingScrollPhysics) ApplyBoundaryConditions
func (BouncingScrollPhysics) ApplyBoundaryConditions(position *ScrollPosition, value float64) float64
ApplyBoundaryConditions still clamps to avoid runaway offsets.
func (BouncingScrollPhysics) ApplyPhysicsToUserOffset
func (BouncingScrollPhysics) ApplyPhysicsToUserOffset(position *ScrollPosition, offset float64) float64
ApplyPhysicsToUserOffset reduces delta when overscrolling.
type Button
Button is a tappable button widget with customizable styling and haptic feedback.
Styling Model
Button is explicit by default — all visual properties (Color, TextColor, Padding, FontSize, BorderRadius) use their struct field values directly. A zero value means zero, not "use theme default." For example:
- Color: 0 means transparent background
- Padding: zero EdgeInsets means no padding
- BorderRadius: 0 means sharp corners
For theme-styled buttons, use [theme.ButtonOf] which pre-fills visual properties from the current theme's [theme.ButtonThemeData].
Creation Patterns
Struct literal (full control):
widgets.Button{
Label: "Submit",
OnTap: handleSubmit,
Color: graphics.RGB(33, 150, 243),
TextColor: graphics.ColorWhite,
Padding: layout.EdgeInsetsSymmetric(24, 14),
BorderRadius: 8,
Haptic: true,
}
Themed (reads from current theme):
theme.ButtonOf(ctx, "Submit", handleSubmit)
// Pre-filled with theme colors, padding, font size, border radius
Themed with overrides:
theme.ButtonOf(ctx, "Submit", handleSubmit).
WithBorderRadius(0). // explicit zero for sharp corners
WithPadding(layout.EdgeInsetsAll(20))
Automatic Features
The button automatically provides:
- Visual feedback on press (opacity change)
- Haptic feedback on tap (when Haptic is true)
- Accessibility support (label announced by screen readers)
- Disabled state handling (when Disabled is true)
type Button struct {
core.StatelessBase
// Label is the text displayed on the button.
Label string
// OnTap is called when the button is tapped. Ignored when Disabled is true.
OnTap func()
// Disabled prevents interaction and applies disabled styling when true.
Disabled bool
// Color is the background color. Zero means transparent.
Color graphics.Color
// Gradient is an optional background gradient that replaces Color when set.
// Ignored when Disabled is true.
Gradient *graphics.Gradient
// TextColor is the label text color. Zero means transparent (invisible text).
TextColor graphics.Color
// FontSize is the label font size in logical pixels. Zero means no text rendered.
FontSize float64
// Padding is the space between the button edge and label.
// Zero means no padding.
Padding layout.EdgeInsets
// BorderRadius is the corner radius in logical pixels.
// Zero means sharp corners.
BorderRadius float64
// Haptic enables haptic feedback on tap when true.
Haptic bool
// DisabledColor is the background color when disabled.
// If zero, falls back to 0.5 opacity on the normal Color.
DisabledColor graphics.Color
// DisabledTextColor is the text color when disabled.
// If zero, falls back to 0.5 opacity on the normal TextColor.
DisabledTextColor graphics.Color
}
Example:
This example shows how to create a basic button with a tap handler.
package main
import (
"fmt"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
button := widgets.Button{
Label: "Click Me",
OnTap: func() {
fmt.Println("Button tapped!")
},
Haptic: true,
}
_ = button
}
Example (With Styles):
This example shows how to customize a button's appearance.
package main
import (
"fmt"
"github.com/go-drift/drift/pkg/graphics"
"github.com/go-drift/drift/pkg/layout"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
button := widgets.Button{
Label: "Submit",
OnTap: func() { fmt.Println("Submitted!") },
Color: graphics.RGB(33, 150, 243),
TextColor: graphics.ColorWhite,
FontSize: 18,
Padding: layout.EdgeInsetsSymmetric(32, 16),
Haptic: true,
}
_ = button
}
func (Button) Build
func (b Button) Build(ctx core.BuildContext) core.Widget
func (Button) WithBorderRadius
func (b Button) WithBorderRadius(radius float64) Button
WithBorderRadius returns a copy of the button with the specified corner radius.
func (Button) WithColor
func (b Button) WithColor(bg, text graphics.Color) Button
WithColor returns a copy of the button with the specified background and text colors.
func (Button) WithDisabled
func (b Button) WithDisabled(disabled bool) Button
WithDisabled returns a copy of the button with the specified disabled state.
func (Button) WithFontSize
func (b Button) WithFontSize(size float64) Button
WithFontSize returns a copy of the button with the specified label font size.
func (Button) WithGradient
func (b Button) WithGradient(gradient *graphics.Gradient) Button
WithGradient returns a copy of the button with the specified background gradient.
func (Button) WithHaptic
func (b Button) WithHaptic(enabled bool) Button
WithHaptic returns a copy of the button with haptic feedback enabled or disabled.
func (Button) WithPadding
func (b Button) WithPadding(padding layout.EdgeInsets) Button
WithPadding returns a copy of the button with the specified padding.
type Center
Center positions its child at the center of the available space.
Center expands to fill available space (like Expanded), then centers the child within that space. The child is given loose constraints, allowing it to size itself.
Center is equivalent to Align{Alignment: layout.AlignmentCenter}.
Example:
Center{Child: Text{Content: "Hello, World!"}}
For more control over alignment, use Container with an Alignment field, or wrap the child in an Align widget.
type Center struct {
core.StatelessBase
Child core.Widget
}
Example:
This example shows how to center a widget.
package main
import (
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
center := widgets.Center{
Child: widgets.Text{Content: "Centered!"},
}
_ = center
}
func Centered
func Centered(child core.Widget) Center
Centered wraps a child in a Center widget.
func (Center) Build
func (c Center) Build(ctx core.BuildContext) core.Widget
type Checkbox
Checkbox displays a toggleable check control with customizable styling.
Styling Model
Checkbox is explicit by default — all visual properties use their struct field values directly. A zero value means zero, not "use theme default." For example:
- ActiveColor: 0 means transparent fill when checked
- Size: 0 means zero size (not rendered)
- BorderRadius: 0 means sharp corners
For theme-styled checkboxes, use [theme.CheckboxOf] which pre-fills visual properties from the current theme's [theme.CheckboxThemeData].
Creation Patterns
Struct literal (full control):
widgets.Checkbox{
Value: isChecked,
OnChanged: func(v bool) { s.SetState(func() { s.isChecked = v }) },
ActiveColor: graphics.RGB(33, 150, 243),
CheckColor: graphics.ColorWhite,
Size: 24,
}
Themed (reads from current theme):
theme.CheckboxOf(ctx, isChecked, onChanged)
// Pre-filled with theme colors, size, border radius
Checkbox is a controlled component - it displays the Value you provide and calls OnChanged when tapped. To toggle the checkbox, update Value in your state in response to OnChanged.
For form integration with validation, wrap in a FormField:
FormField[bool]{
InitialValue: false,
Validator: func(v bool) string {
if !v { return "Must accept terms" }
return ""
},
Builder: func(state *FormFieldState[bool]) core.Widget {
return Checkbox{Value: state.Value(), OnChanged: state.DidChange}
},
}
type Checkbox struct {
core.StatelessBase
// Value indicates whether the checkbox is checked.
Value bool
// OnChanged is called when the checkbox is toggled.
OnChanged func(bool)
// Disabled disables interaction when true.
Disabled bool
// Size controls the checkbox square size. Zero means zero size (not rendered).
Size float64
// BorderRadius controls the checkbox corner radius. Zero means sharp corners.
BorderRadius float64
// ActiveColor is the fill color when checked. Zero means transparent.
ActiveColor graphics.Color
// CheckColor is the checkmark color. Zero means transparent (invisible check).
CheckColor graphics.Color
// BorderColor is the outline color. Zero means no border.
BorderColor graphics.Color
// BackgroundColor is the fill color when unchecked. Zero means transparent.
BackgroundColor graphics.Color
// DisabledActiveColor is the fill color when checked and disabled.
// If zero, falls back to 0.5 opacity on the normal colors.
DisabledActiveColor graphics.Color
// DisabledCheckColor is the checkmark color when disabled.
// If zero, falls back to 0.5 opacity on the normal colors.
DisabledCheckColor graphics.Color
}
Example:
This example shows how to create a checkbox form control.
package main
import (
"fmt"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
var isChecked bool
checkbox := widgets.Checkbox{
Value: isChecked,
OnChanged: func(value bool) {
isChecked = value
fmt.Printf("Checkbox is now: %v\n", isChecked)
},
Size: 24,
BorderRadius: 4,
}
_ = checkbox
}
func (Checkbox) Build
func (c Checkbox) Build(ctx core.BuildContext) core.Widget
func (Checkbox) WithBackgroundColor
func (c Checkbox) WithBackgroundColor(color graphics.Color) Checkbox
WithBackgroundColor returns a copy of the checkbox with the specified unchecked fill color.
func (Checkbox) WithBorderColor
func (c Checkbox) WithBorderColor(color graphics.Color) Checkbox
WithBorderColor returns a copy of the checkbox with the specified outline color.
func (Checkbox) WithBorderRadius
func (c Checkbox) WithBorderRadius(radius float64) Checkbox
WithBorderRadius returns a copy of the checkbox with the specified corner radius.
func (Checkbox) WithColors
func (c Checkbox) WithColors(activeColor, checkColor graphics.Color) Checkbox
WithColors returns a copy of the checkbox with the specified active fill and checkmark colors.
func (Checkbox) WithSize
func (c Checkbox) WithSize(size float64) Checkbox
WithSize returns a copy of the checkbox with the specified square size.
type CircularProgressIndicator
CircularProgressIndicator displays a circular progress indicator. When Value is nil, it shows an indeterminate animation. When Value is set, it shows determinate progress from 0.0 to 1.0.
Styling Model
CircularProgressIndicator is explicit by default — zero values mean zero (no color). For theme-styled indicators, use [theme.CircularProgressIndicatorOf] which pre-fills Color from Primary and TrackColor from SurfaceVariant.
Creation Patterns
Struct literal (full control):
widgets.CircularProgressIndicator{
Value: nil, // indeterminate
Color: colors.Primary,
TrackColor: colors.SurfaceVariant,
Size: 36,
StrokeWidth: 4,
}
Themed (reads from current theme):
theme.CircularProgressIndicatorOf(ctx, nil) // indeterminate
theme.CircularProgressIndicatorOf(ctx, &progress) // determinate
type CircularProgressIndicator struct {
core.StatefulBase
// Value is the progress value (0.0 to 1.0). Nil means indeterminate.
Value *float64
// Color is the indicator color. Zero means transparent (invisible).
Color graphics.Color
// TrackColor is the background track color. Zero means no track.
TrackColor graphics.Color
// StrokeWidth is the thickness of the indicator. Zero means zero stroke (invisible).
StrokeWidth float64
// Size is the diameter of the indicator. Zero means zero size (not rendered).
Size float64
}
func (CircularProgressIndicator) CreateState
func (c CircularProgressIndicator) CreateState() core.State
type ClampingScrollPhysics
ClampingScrollPhysics clamps at edges (Android default). AllowsOverscroll returns false.
type ClampingScrollPhysics struct{}
func (ClampingScrollPhysics) AllowsOverscroll
func (ClampingScrollPhysics) AllowsOverscroll() bool
AllowsOverscroll returns false; clamping physics stop at content boundaries.
func (ClampingScrollPhysics) ApplyBoundaryConditions
func (ClampingScrollPhysics) ApplyBoundaryConditions(position *ScrollPosition, value float64) float64
ApplyBoundaryConditions clamps at the min/max extents.
func (ClampingScrollPhysics) ApplyPhysicsToUserOffset
func (ClampingScrollPhysics) ApplyPhysicsToUserOffset(_ *ScrollPosition, offset float64) float64
ApplyPhysicsToUserOffset returns the raw delta for clamping physics.
type ClipRRect
ClipRRect clips its child using rounded corners.
type ClipRRect struct {
core.RenderObjectBase
Child core.Widget
Radius float64
}
func (ClipRRect) ChildWidget
func (c ClipRRect) ChildWidget() core.Widget
func (ClipRRect) CreateRenderObject
func (c ClipRRect) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
func (ClipRRect) UpdateRenderObject
func (c ClipRRect) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
type Column
Column lays out children vertically from top to bottom.
Column is a flex container where the main axis is vertical. Children are laid out in a single vertical run and do not wrap.
Sizing Behavior
By default, Column expands to fill available vertical space. Set MainAxisSizeMin to shrink-wrap the children instead.
Alignment
Use MainAxisAlignment to control vertical spacing (Start, End, Center, SpaceBetween, SpaceAround, SpaceEvenly). Use CrossAxisAlignment to control horizontal alignment (Start, End, Center, Stretch).
Flexible Children
Wrap children in Expanded to make them share remaining space proportionally:
Column{
Children: []core.Widget{
Text{Content: "Header"},
Expanded{Child: ListView{...}}, // Takes remaining space
Text{Content: "Footer"},
},
}
For horizontal layout, use Row.
type Column struct {
core.RenderObjectBase
Children []core.Widget
MainAxisAlignment MainAxisAlignment
CrossAxisAlignment CrossAxisAlignment
MainAxisSize MainAxisSize
}
Example:
This example shows how to create a vertical layout with Column.
package main
import (
"github.com/go-drift/drift/pkg/core"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
column := widgets.Column{
Children: []core.Widget{
widgets.Text{Content: "First"},
widgets.Text{Content: "Second"},
widgets.Text{Content: "Third"},
},
MainAxisAlignment: widgets.MainAxisAlignmentStart,
CrossAxisAlignment: widgets.CrossAxisAlignmentStretch,
}
_ = column
}
Example (Centered):
This example shows creating a column with struct literal.
package main
import (
"github.com/go-drift/drift/pkg/core"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
column := widgets.Column{
MainAxisAlignment: widgets.MainAxisAlignmentCenter,
CrossAxisAlignment: widgets.CrossAxisAlignmentCenter,
MainAxisSize: widgets.MainAxisSizeMin,
Children: []core.Widget{
widgets.Text{Content: "Hello"},
widgets.VSpace(16),
widgets.Text{Content: "World"},
},
}
_ = column
}
Example (With Expanded):
This example shows flexible children in a vertical layout.
package main
import (
"github.com/go-drift/drift/pkg/core"
"github.com/go-drift/drift/pkg/graphics"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
column := widgets.Column{
Children: []core.Widget{
// Fixed header
widgets.Container{Height: 60, Color: graphics.RGB(33, 33, 33)},
// Content takes remaining space
widgets.Expanded{
Child: widgets.ScrollView{
Child: widgets.Text{Content: "Scrollable content..."},
},
},
// Fixed footer
widgets.Container{Height: 48, Color: graphics.RGB(66, 66, 66)},
},
}
_ = column
}
func (Column) ChildrenWidgets
func (c Column) ChildrenWidgets() []core.Widget
func (Column) CreateRenderObject
func (c Column) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
func (Column) UpdateRenderObject
func (c Column) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
type Container
Container is a convenience widget that combines common painting, positioning, and sizing operations into a single widget.
Container applies decorations in this order:
- Shadow (drawn behind the container, naturally overflows bounds)
- Background color or gradient (clipped to BorderRadius when Overflow is OverflowClip)
- Border stroke (if BorderWidth > 0)
- Child widget (clipped to bounds when Overflow is OverflowClip)
Sizing Behavior
Without explicit Width/Height, Container sizes to fit its child plus padding. With Width and/or Height set, those dimensions become the container's preferred size (subject to parent constraints) and set maximum child size on those axes. Children can be smaller than the container, and Alignment controls their placement within the available content area (after padding).
Note: Parent constraints take precedence. If a parent imposes tight constraints larger than Width/Height, the container will expand to meet those constraints.
Common Patterns
// Rounded card with padding
Container{
Color: colors.Surface,
BorderRadius: 12,
Padding: layout.EdgeInsetsAll(16),
Child: content,
}
// Bordered box
Container{
BorderColor: colors.Outline,
BorderWidth: 1,
BorderRadius: 8,
Padding: layout.EdgeInsetsAll(12),
Child: Text{Content: "Hello"},
}
// Fixed-size centered child
Container{
Width: 200,
Height: 100,
Alignment: layout.AlignmentCenter,
Child: icon,
}
Container supports all DecoratedBox features. For decoration without layout behavior (no padding, sizing, or alignment), use DecoratedBox directly.
type Container struct {
core.RenderObjectBase
Child core.Widget
Padding layout.EdgeInsets
Width float64
Height float64
Color graphics.Color
Gradient *graphics.Gradient
Alignment layout.Alignment
Shadow *graphics.BoxShadow
// Border
BorderColor graphics.Color // Border stroke color; transparent = no border
BorderWidth float64 // Border stroke width in pixels; 0 = no border
BorderRadius float64 // Corner radius for rounded rectangles; 0 = sharp corners
BorderDash *graphics.DashPattern // Dash pattern for border; nil = solid line
// BorderGradient applies a gradient to the border stroke. When set, overrides
// BorderColor. Requires BorderWidth > 0 to be visible. Works with BorderDash
// for dashed gradient borders.
BorderGradient *graphics.Gradient
// Overflow controls clipping behavior for gradients and children.
// Defaults to OverflowClip, which confines gradients and children strictly
// within bounds (clipped to rounded shape when BorderRadius > 0).
// Set to OverflowVisible for glow effects where the gradient should extend
// beyond the container; children will not be clipped.
// Shadows always overflow naturally. Solid background colors never overflow.
Overflow Overflow
}
Example:
This example shows how to create a styled container.
package main
import (
"github.com/go-drift/drift/pkg/graphics"
"github.com/go-drift/drift/pkg/layout"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
container := widgets.Container{
Padding: layout.EdgeInsetsAll(16),
Color: graphics.RGB(245, 245, 245),
Width: 200,
Height: 100,
Child: widgets.Text{
Content: "Centered content",
},
Alignment: layout.AlignmentCenter,
}
_ = container
}
Example (With Gradient):
This example shows a container with gradient and shadow.
package main
import (
"github.com/go-drift/drift/pkg/graphics"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
container := widgets.Container{
Width: 200,
Height: 100,
Gradient: graphics.NewLinearGradient(
graphics.AlignTopLeft,
graphics.AlignBottomRight,
[]graphics.GradientStop{
{Position: 0.0, Color: graphics.RGB(66, 133, 244)},
{Position: 1.0, Color: graphics.RGB(15, 157, 88)},
},
),
Shadow: &graphics.BoxShadow{
Color: graphics.RGBA(0, 0, 0, 0.25),
BlurRadius: 8,
Offset: graphics.Offset{X: 0, Y: 4},
},
Child: widgets.Center{Child: widgets.Text{Content: "Gradient Card"}},
}
_ = container
}
func (Container) ChildWidget
func (c Container) ChildWidget() core.Widget
func (Container) CreateRenderObject
func (c Container) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
func (Container) UpdateRenderObject
func (c Container) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
func (Container) WithAlignment
func (c Container) WithAlignment(alignment layout.Alignment) Container
WithAlignment returns a copy of the container with the specified child alignment.
func (Container) WithBorder
func (c Container) WithBorder(color graphics.Color, width float64) Container
WithBorder returns a copy of the container with the specified border color and width.
func (Container) WithBorderGradient
func (c Container) WithBorderGradient(gradient *graphics.Gradient) Container
WithBorderGradient returns a copy with the specified border gradient. The gradient overrides BorderColor when both are set.
func (Container) WithBorderRadius
func (c Container) WithBorderRadius(radius float64) Container
WithBorderRadius returns a copy of the container with the specified corner radius.
func (Container) WithColor
func (c Container) WithColor(color graphics.Color) Container
WithColor returns a copy of the container with the specified background color.
func (Container) WithGradient
func (c Container) WithGradient(gradient *graphics.Gradient) Container
WithGradient returns a copy of the container with the specified background gradient.
func (Container) WithPadding
func (c Container) WithPadding(padding layout.EdgeInsets) Container
WithPadding returns a copy of the container with the specified padding.
func (Container) WithSize
func (c Container) WithSize(width, height float64) Container
WithSize returns a copy of the container with the specified width and height.
type CrossAxisAlignment
CrossAxisAlignment controls how children are positioned along the cross axis (vertical for Row, horizontal for Column).
type CrossAxisAlignment int
const (
// CrossAxisAlignmentStart places children at the start of the cross axis.
CrossAxisAlignmentStart CrossAxisAlignment = iota
// CrossAxisAlignmentEnd places children at the end of the cross axis.
CrossAxisAlignmentEnd
// CrossAxisAlignmentCenter centers children along the cross axis.
CrossAxisAlignmentCenter
// CrossAxisAlignmentStretch stretches children to fill the cross axis.
CrossAxisAlignmentStretch
)
func (CrossAxisAlignment) String
func (a CrossAxisAlignment) String() string
String returns a human-readable representation of the cross axis alignment.
type DatePicker
DatePicker displays a date selection field that opens a native date picker modal.
type DatePicker struct {
core.StatefulBase
// Value is the current selected date (nil = no selection).
Value *time.Time
// OnChanged is called when the user selects a date.
OnChanged func(time.Time)
// Disabled disables interaction when true.
Disabled bool
// MinDate is the minimum selectable date (optional).
MinDate *time.Time
// MaxDate is the maximum selectable date (optional).
MaxDate *time.Time
// Format is the date format string (Go time format). Default: "Jan 2, 2006"
Format string
// Placeholder is shown when Value is nil.
Placeholder string
// Decoration provides styling (label, hint, border, icons, etc.).
Decoration *InputDecoration
// TextStyle for the value text.
TextStyle graphics.TextStyle
// Child overrides the default rendering for full customization.
Child core.Widget
}
func (DatePicker) CreateState
func (d DatePicker) CreateState() core.State
type DebugErrorScreen
DebugErrorScreen is a full-screen error display for development mode. It shows detailed error information including:
- Error phase (Build, Layout, Paint, HitTest, Frame, Pointer)
- Widget or RenderObject type that failed
- Error message
- Scrollable stack trace
- Restart button to recover the app
This screen is automatically shown in debug mode (core.DebugMode = true) when an uncaught panic occurs. In production mode, panics crash the app unless caught by an ErrorBoundary.
The restart button calls [engine.RestartApp] to unmount the entire widget tree and re-mount from scratch, clearing all state.
type DebugErrorScreen struct {
core.StatelessBase
// Error is the boundary error to display. If nil, shows "Unknown error".
Error *errors.BoundaryError
}
func (DebugErrorScreen) Build
func (d DebugErrorScreen) Build(ctx core.BuildContext) core.Widget
type DecoratedBox
DecoratedBox paints a background, border, and shadow behind its child.
DecoratedBox applies decorations in this order:
- Shadow (drawn behind, naturally overflows bounds)
- Background color or gradient (overflow controlled by Overflow field)
- Border stroke (drawn on top of background, supports dashing)
- Child widget (clipped to bounds when Overflow is OverflowClip)
Use BorderRadius for rounded corners. The Overflow field controls clipping:
- OverflowClip (default): gradient and children clipped to bounds
- OverflowVisible: gradient can overflow, children not clipped
With OverflowClip, children are clipped to the widget bounds (rounded rectangle when BorderRadius > 0). This ensures content like images or accent bars at the edges conform to the bounds without needing a separate ClipRRect.
Note: Platform views (native text fields, etc.) are clipped to rectangular bounds only, not rounded corners. This is a platform limitation.
For combined layout and decoration (padding, sizing, alignment), use Container which composes DecoratedBox internally. Use DecoratedBox directly when you need decoration without any layout behavior.
type DecoratedBox struct {
core.RenderObjectBase
Child core.Widget // Child widget to display inside the decoration
// Background
Color graphics.Color // Background fill color
Gradient *graphics.Gradient // Background gradient; overrides Color if set
// Border
BorderColor graphics.Color // Border stroke color; transparent = no border
BorderWidth float64 // Border stroke width in pixels; 0 = no border
BorderRadius float64 // Corner radius for rounded rectangles; 0 = sharp corners
BorderDash *graphics.DashPattern // Dash pattern for border; nil = solid line
// BorderGradient applies a gradient to the border stroke. When set, overrides
// BorderColor. Requires BorderWidth > 0 to be visible. Works with BorderDash
// for dashed gradient borders.
BorderGradient *graphics.Gradient
// Effects
Shadow *graphics.BoxShadow // Drop shadow drawn behind the box; nil = no shadow
// Overflow controls clipping behavior for gradients and children.
// Defaults to OverflowClip, which confines gradients and children strictly
// within bounds (clipped to rounded shape when BorderRadius > 0).
// Set to OverflowVisible for glow effects where the gradient should extend
// beyond the widget; children will not be clipped.
// Shadows always overflow naturally. Solid background colors never overflow.
Overflow Overflow
}
func (DecoratedBox) ChildWidget
func (d DecoratedBox) ChildWidget() core.Widget
func (DecoratedBox) CreateRenderObject
func (d DecoratedBox) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
func (DecoratedBox) UpdateRenderObject
func (d DecoratedBox) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
type DeviceScale
DeviceScale provides the current device pixel scale factor to descendants.
type DeviceScale struct {
core.InheritedBase
Scale float64
Child core.Widget
}
func (DeviceScale) ChildWidget
func (d DeviceScale) ChildWidget() core.Widget
func (DeviceScale) ShouldRebuildDependents
func (d DeviceScale) ShouldRebuildDependents(oldWidget core.InheritedWidget) bool
type DiagnosticsHUD
DiagnosticsHUD displays performance metrics overlay.
type DiagnosticsHUD struct {
core.StatelessBase
// DataSource provides frame timing data.
DataSource DiagnosticsHUDDataSource
// TargetTime is the target frame duration for coloring the graph.
TargetTime time.Duration
// GraphWidth is the width of the frame graph. Defaults to 120.
GraphWidth float64
// GraphHeight is the height of the frame graph. Defaults to 60.
GraphHeight float64
// ShowFPS controls whether to display the FPS counter.
ShowFPS bool
// ShowFrameGraph controls whether to display the frame time graph.
ShowFrameGraph bool
}
func (DiagnosticsHUD) Build
func (d DiagnosticsHUD) Build(ctx core.BuildContext) core.Widget
type DiagnosticsHUDDataSource
DiagnosticsHUDDataSource provides frame timing data to the HUD.
type DiagnosticsHUDDataSource interface {
// FPSLabel returns the current FPS display string.
FPSLabel() string
// SamplesInto copies frame samples into dst and returns count copied.
SamplesInto(dst []time.Duration) int
// SampleCount returns the number of samples available.
SampleCount() int
// RegisterRenderObject registers the HUD render object for targeted repaints.
RegisterRenderObject(ro layout.RenderObject)
}
type Divider
Divider renders a thin horizontal line with optional insets and spacing.
Divider is typically used as a child of a Column or any vertically-stacked layout. It expands to fill the available width and occupies Height pixels of vertical space, drawing a centered line of the given Thickness.
Styling Model
Divider is explicit by default: all visual properties use their struct field values directly. A zero value means zero, not "use theme default." For example:
- Height: 0 means zero vertical space (not rendered)
- Thickness: 0 means no visible line
- Color: 0 means transparent (invisible)
For theme-styled dividers, use [theme.DividerOf] which pre-fills values from the current theme's [DividerThemeData] (color from OutlineVariant, 16px space, 1px thickness).
Creation Patterns
Struct literal (full control):
widgets.Divider{
Height: 16,
Thickness: 1,
Color: graphics.RGB(200, 200, 200),
Indent: 16, // 16px left inset
}
Themed (reads from current theme):
theme.DividerOf(ctx)
type Divider struct {
core.RenderObjectBase
// Height is the total vertical space the divider occupies.
Height float64
// Thickness is the thickness of the drawn line.
Thickness float64
// Color is the line color. Zero means transparent.
Color graphics.Color
// Indent is the left inset from the leading edge.
Indent float64
// EndIndent is the right inset from the trailing edge.
EndIndent float64
}
func (Divider) CreateRenderObject
func (d Divider) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
func (Divider) UpdateRenderObject
func (d Divider) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
type DragEndDetails
DragEndDetails describes the end of a drag.
type DragEndDetails = gestures.DragEndDetails
type DragMode
DragMode controls how drag gestures interact with bottom sheet content.
type DragMode int
const (
// DragModeAuto picks DragModeContentAware for multi-snap sheets and DragModeSheet otherwise.
DragModeAuto DragMode = iota
// DragModeContentAware coordinates drags with scrollable content when possible.
DragModeContentAware
// DragModeSheet drags the entire sheet regardless of content.
DragModeSheet
// DragModeHandleOnly only accepts drags from the handle area.
DragModeHandleOnly
)
type DragStartDetails
DragStartDetails describes the start of a drag.
type DragStartDetails = gestures.DragStartDetails
type DragUpdateDetails
DragUpdateDetails describes a drag update.
type DragUpdateDetails = gestures.DragUpdateDetails
type Dropdown
Dropdown displays a button that opens a menu of selectable items.
Styling Model
Dropdown is explicit by default — all visual properties use their struct field values directly. A zero value means zero, not "use theme default." For example:
- BackgroundColor: 0 means transparent background
- BorderRadius: 0 means sharp corners
- Height: 0 means zero height (not rendered)
For theme-styled dropdowns, use [theme.DropdownOf] which pre-fills visual properties from the current theme's [theme.DropdownThemeData].
Creation Patterns
Explicit with struct literal (full control):
widgets.Dropdown[string]{
Value: selectedCountry,
Items: countryItems,
OnChanged: func(v string) { s.SetState(func() { s.selectedCountry = v }) },
BackgroundColor: graphics.ColorWhite,
BorderColor: graphics.RGB(200, 200, 200),
BorderRadius: 8,
Height: 48,
}
Themed (reads from current theme):
theme.DropdownOf(ctx, selectedCountry, countryItems, onChanged)
// Pre-filled with theme colors, border radius, height, item padding
Dropdown is a generic widget where T is the type of the selection value. When an item is selected, OnChanged is called with the selected item's Value.
Each DropdownItem can have a custom Child instead of a text Label. Items can be individually disabled by setting Disabled: true.
type Dropdown[T comparable] struct {
core.StatefulBase
// Value is the current selected value.
Value T
// Items are the available selections.
Items []DropdownItem[T]
// OnChanged is called when a new value is selected.
OnChanged func(T)
// Hint is shown when no selection matches.
Hint string
// Disabled disables the dropdown when true.
Disabled bool
// Width sets a fixed width (0 uses layout constraints).
Width float64
// Height sets a fixed height. Zero means zero height (not rendered).
Height float64
// BorderRadius sets the corner radius. Zero means sharp corners.
BorderRadius float64
// BackgroundColor sets the trigger background. Zero means transparent.
BackgroundColor graphics.Color
// BorderColor sets the trigger border color. Zero means no border.
BorderColor graphics.Color
// MenuBackgroundColor sets the menu background. Zero means transparent.
MenuBackgroundColor graphics.Color
// MenuBorderColor sets the menu border color. Zero means no border.
MenuBorderColor graphics.Color
// TextStyle sets the text style for labels.
TextStyle graphics.TextStyle
// ItemPadding sets padding for each menu item. Zero means no padding.
ItemPadding layout.EdgeInsets
// SelectedItemColor is the background for the currently selected item.
SelectedItemColor graphics.Color
// DisabledTextColor is the text color when disabled.
// If zero, falls back to 0.5 opacity on the normal styling.
DisabledTextColor graphics.Color
}
Example:
This example shows a dropdown selection menu.
package main
import (
"fmt"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
var selectedCountry string
dropdown := widgets.Dropdown[string]{
Value: selectedCountry,
Hint: "Select a country",
Items: []widgets.DropdownItem[string]{
{Value: "us", Label: "United States"},
{Value: "ca", Label: "Canada"},
{Value: "mx", Label: "Mexico"},
},
OnChanged: func(value string) {
selectedCountry = value
fmt.Printf("Selected: %s\n", selectedCountry)
},
}
_ = dropdown
}
func (Dropdown[T]) CreateState
func (d Dropdown[T]) CreateState() core.State
func (Dropdown[T]) WithBackgroundColor
func (d Dropdown[T]) WithBackgroundColor(c graphics.Color) Dropdown[T]
WithBackgroundColor returns a copy with the specified trigger background color.
func (Dropdown[T]) WithBorderColor
func (d Dropdown[T]) WithBorderColor(c graphics.Color) Dropdown[T]
WithBorderColor returns a copy with the specified trigger border color.
func (Dropdown[T]) WithBorderRadius
func (d Dropdown[T]) WithBorderRadius(radius float64) Dropdown[T]
WithBorderRadius returns a copy with the specified corner radius.
func (Dropdown[T]) WithHeight
func (d Dropdown[T]) WithHeight(height float64) Dropdown[T]
WithHeight returns a copy with the specified item row height.
func (Dropdown[T]) WithHint
func (d Dropdown[T]) WithHint(hint string) Dropdown[T]
WithHint returns a copy with the specified hint text shown when no selection matches.
func (Dropdown[T]) WithItemPadding
func (d Dropdown[T]) WithItemPadding(padding layout.EdgeInsets) Dropdown[T]
WithItemPadding returns a copy with the specified menu item padding.
func (Dropdown[T]) WithMenuBackgroundColor
func (d Dropdown[T]) WithMenuBackgroundColor(c graphics.Color) Dropdown[T]
WithMenuBackgroundColor returns a copy with the specified menu panel background color.
func (Dropdown[T]) WithMenuBorderColor
func (d Dropdown[T]) WithMenuBorderColor(c graphics.Color) Dropdown[T]
WithMenuBorderColor returns a copy with the specified menu panel border color.
type DropdownItem
DropdownItem represents a selectable value for a dropdown.
type DropdownItem[T comparable] struct {
// Value is the item value.
Value T
// Label is the text shown for the item.
Label string
// Child overrides the label when provided.
Child core.Widget
// Disabled disables selection when true.
Disabled bool
}
type ErrorBoundary
ErrorBoundary catches panics from descendant widgets and displays a fallback widget instead of crashing the app. This provides scoped error handling for subtrees of the widget tree.
Error Handling Behavior
In debug mode (core.DebugMode = true), uncaught panics anywhere in the app display a full-screen DebugErrorScreen with stack traces. In production mode, uncaught panics crash the app. Use ErrorBoundary to catch panics and show graceful fallback UI in production.
ErrorBoundary catches panics during:
- Build: widget Build() methods
- Layout: RenderObject PerformLayout()
- Paint: RenderObject Paint()
- HitTest: RenderObject HitTest()
Scoped vs Global Error Handling
Wrap specific subtrees to isolate failures while keeping the rest of the app running. Or wrap your entire app to provide custom error UI in production:
// Scoped: only RiskyWidget failures show fallback
Column{
Children: []core.Widget{
HeaderWidget{},
ErrorBoundary{
Child: RiskyWidget{},
FallbackBuilder: func(err *errors.BoundaryError) core.Widget {
return Text{Content: "Failed to load"}
},
},
FooterWidget{},
},
}
// Global: custom error UI for entire app in production
drift.NewApp(ErrorBoundary{
Child: MyApp{},
FallbackBuilder: func(err *errors.BoundaryError) core.Widget {
return MyCustomErrorScreen{Error: err}
},
}).Run()
Programmatic Control
Use [ErrorBoundaryOf] to access the boundary's state from descendant widgets:
state := widgets.ErrorBoundaryOf(ctx)
if state != nil && state.HasError() {
state.Reset() // Clear error and retry
}
type ErrorBoundary struct {
core.StatefulBase
// Child is the widget tree to wrap with error handling.
Child core.Widget
// FallbackBuilder creates a widget to show when an error is caught.
// If nil, uses the default ErrorWidget.
FallbackBuilder func(*errors.BoundaryError) core.Widget
// OnError is called when an error is caught. Use for logging/analytics.
OnError func(*errors.BoundaryError)
// WidgetKey is an optional key for the widget. Changing the key forces
// the ErrorBoundary to recreate its state, clearing any captured error.
WidgetKey any
}
Example:
This example shows error boundary for catching widget errors.
package main
import (
"fmt"
"github.com/go-drift/drift/pkg/core"
drifterrors "github.com/go-drift/drift/pkg/errors"
"github.com/go-drift/drift/pkg/graphics"
"github.com/go-drift/drift/pkg/layout"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
boundary := widgets.ErrorBoundary{
OnError: func(err *drifterrors.BoundaryError) {
fmt.Printf("Widget error: %v\n", err)
},
FallbackBuilder: func(err *drifterrors.BoundaryError) core.Widget {
return widgets.Container{
Padding: layout.EdgeInsetsAll(16),
Color: graphics.RGBA(255, 0, 0, 0.13),
Child: widgets.Text{Content: "Something went wrong"},
}
},
Child: widgets.Text{Content: "Protected content"},
}
_ = boundary
}
func (ErrorBoundary) CreateState
func (e ErrorBoundary) CreateState() core.State
func (ErrorBoundary) Key
func (e ErrorBoundary) Key() any
type ErrorWidget
ErrorWidget displays inline error information when a widget fails. Unlike DebugErrorScreen which takes over the entire screen, ErrorWidget renders as a compact red box that can be embedded in layouts.
It shows a red background with:
- "Something went wrong" message
- Detailed error text (in debug mode or when Verbose is true)
- Restart button to recover the app
This is the default fallback widget used by ErrorBoundary when no FallbackBuilder is provided.
type ErrorWidget struct {
core.StatelessBase
// Error is the boundary error that occurred. If nil, shows "Unknown error".
Error *errors.BoundaryError
// Verbose overrides DebugMode for this widget instance.
// When true, shows detailed error messages. When false, shows generic text.
// If nil (default), uses core.DebugMode.
Verbose *bool
}
func (ErrorWidget) Build
func (e ErrorWidget) Build(ctx core.BuildContext) core.Widget
type ExcludeSemantics
ExcludeSemantics is a widget that excludes its child from the semantics tree. Use this to hide decorative elements from accessibility services.
type ExcludeSemantics struct {
core.RenderObjectBase
// Child is the child widget to exclude.
Child core.Widget
// Excluding controls whether to exclude the child from semantics.
// Set to true to exclude, false to include. Defaults to false (Go zero value).
// For the common case of excluding, use: ExcludeSemantics{Excluding: true, Child: child}
Excluding bool
}
func Decorative
func Decorative(child core.Widget) ExcludeSemantics
Decorative marks a widget as decorative, hiding it from screen readers. Use this for purely visual elements that don't convey information.
Example:
Decorative(dividerLine)
Example:
This example shows how to hide decorative elements from accessibility.
package main
import (
"github.com/go-drift/drift/pkg/graphics"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
// Hides purely visual elements from screen readers
divider := widgets.Decorative(
widgets.Container{
Height: 1,
Color: graphics.RGB(200, 200, 200),
},
)
_ = divider
}
func NewExcludeSemantics
func NewExcludeSemantics(child core.Widget) ExcludeSemantics
NewExcludeSemantics creates an ExcludeSemantics widget that excludes the child from accessibility.
func (ExcludeSemantics) ChildWidget
func (e ExcludeSemantics) ChildWidget() core.Widget
ChildWidget returns the child widget.
func (ExcludeSemantics) CreateRenderObject
func (e ExcludeSemantics) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
func (ExcludeSemantics) UpdateRenderObject
func (e ExcludeSemantics) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
type Expanded
Expanded makes its child fill all remaining space along the main axis of a Row or Column.
After non-flexible children are laid out, remaining space is distributed among Expanded children proportionally based on their Flex factor. The default Flex is 1; set higher values to allocate more space to specific children.
Expanded is equivalent to Flexible with Fit set to FlexFitTight. Use Flexible instead when the child should be allowed to be smaller than its allocated space.
Note: With MainAxisSizeMin, there is no remaining space to fill, so Expanded children receive zero space. Using Expanded inside a ScrollView (unbounded main axis) will panic, since there is no finite space to divide.
Example
Fill remaining space between fixed-size widgets:
Row{
Children: []core.Widget{
Icon{...}, // Fixed size
Expanded{Child: Text{Content: "..."}}, // Fills remaining space
Button{...}, // Fixed size
},
}
Example with Flex Factors
Distribute space proportionally among multiple Expanded children:
Row{
Children: []core.Widget{
Expanded{Flex: 1, Child: panelA}, // Gets 1/3 of space
Expanded{Flex: 2, Child: panelB}, // Gets 2/3 of space
},
}
type Expanded struct {
core.RenderObjectBase
// Child is the widget to expand into the available space.
Child core.Widget
// Flex determines the ratio of space allocated to this child relative to
// other flexible children. Defaults to 1 if not set or <= 0.
//
// For example, in a Row with two Expanded children where one has Flex: 1
// and the other has Flex: 2, the remaining space is split 1:2.
Flex int
}
Example:
This example shows how to use Expanded for flexible sizing.
package main
import (
"github.com/go-drift/drift/pkg/core"
"github.com/go-drift/drift/pkg/graphics"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
row := widgets.Row{
Children: []core.Widget{
widgets.Text{Content: "Label:"},
widgets.HSpace(8),
// Expanded takes all remaining horizontal space
widgets.Expanded{
Child: widgets.Container{
Color: graphics.RGB(240, 240, 240),
Child: widgets.Text{Content: "Flexible content"},
},
},
},
}
_ = row
}
Example (Flex Factors):
This example shows proportional sizing with flex factors.
package main
import (
"github.com/go-drift/drift/pkg/core"
"github.com/go-drift/drift/pkg/graphics"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
row := widgets.Row{
Children: []core.Widget{
// Takes 1/3 of available space
widgets.Expanded{
Flex: 1,
Child: widgets.Container{Color: graphics.RGB(255, 0, 0)},
},
// Takes 2/3 of available space
widgets.Expanded{
Flex: 2,
Child: widgets.Container{Color: graphics.RGB(0, 0, 255)},
},
},
}
_ = row
}
func Spacer
func Spacer() Expanded
Spacer fills remaining space along the main axis of a Row or Column. It is equivalent to Expanded{Child: SizedBox{}}.
func (Expanded) ChildWidget
func (e Expanded) ChildWidget() core.Widget
ChildWidget returns the child widget.
func (Expanded) CreateRenderObject
func (e Expanded) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
CreateRenderObject creates the renderFlexChild.
func (Expanded) UpdateRenderObject
func (e Expanded) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
UpdateRenderObject updates the renderFlexChild.
type FlexFactor
FlexFactor reports the flex value for a render box.
type FlexFactor interface {
FlexFactor() int
}
type FlexFit
FlexFit controls how a flexible child fills its allocated space within a Row or Column.
When a flex container distributes remaining space among flexible children, FlexFit determines whether each child must fill its allocated portion (tight) or may be smaller (loose).
See Flexible and Expanded for widgets that use FlexFit.
type FlexFit int
const (
// FlexFitLoose allows the child to size itself up to the allocated space,
// but permits it to be smaller. The child receives constraints with
// MinWidth/MinHeight of 0 and MaxWidth/MaxHeight of the allocated space.
//
// This is the zero value, making it the default for [Flexible].
FlexFitLoose FlexFit = iota
// FlexFitTight forces the child to fill exactly the allocated space.
// The child receives tight constraints where Min equals Max for the
// main axis dimension.
//
// This is the behavior of [Expanded].
FlexFitTight
)
type FlexFitProvider
FlexFitProvider reports the fit mode for a flexible render box.
Render objects used as flex children can implement this interface to control whether they receive tight or loose constraints from the parent Row or Column.
Render objects that don't implement this interface default to FlexFitTight for backward compatibility with existing Expanded behavior.
type FlexFitProvider interface {
FlexFit() FlexFit
}
type Flexible
Flexible allows its child to participate in flex space distribution within a Row or Column without requiring the child to fill all allocated space.
Fit Behavior
By default (zero value), Flexible uses FlexFitLoose, which allows the child to be smaller than its allocated space. The child receives loose constraints where MinWidth/MinHeight is 0 and MaxWidth/MaxHeight is the allocated space.
Set Fit to FlexFitTight for behavior equivalent to Expanded, where the child must fill exactly its allocated space.
When to Use Flexible vs Expanded
Use Flexible when the child should participate in flex distribution but may not need all allocated space (e.g., text that might be short).
Use Expanded when the child must fill all remaining space (e.g., a panel or container that should stretch).
Example
A row where text takes only the space it needs, while a panel fills the rest:
Row{
Children: []core.Widget{
Flexible{Child: Text{Content: "Short"}}, // Uses only needed width
Expanded{Child: panel}, // Fills remaining space
},
}
Example with Flex Factors
Distribute space proportionally while allowing children to be smaller:
Row{
Children: []core.Widget{
Flexible{Flex: 1, Child: smallWidget}, // Gets up to 1/3 of space
Flexible{Flex: 2, Child: largeWidget}, // Gets up to 2/3 of space
},
}
type Flexible struct {
core.RenderObjectBase
// Child is the widget to display within the flexible space.
Child core.Widget
// Flex determines the ratio of space allocated to this child relative to
// other flexible children. Defaults to 1 if not set or <= 0.
//
// For example, in a Row with two Flexible children where one has Flex: 1
// and the other has Flex: 2, the remaining space is split 1:2.
Flex int
// Fit controls whether the child must fill its allocated space.
// The zero value is [FlexFitLoose], allowing the child to be smaller.
// Set to [FlexFitTight] for behavior equivalent to [Expanded].
Fit FlexFit
}
func (Flexible) ChildWidget
func (f Flexible) ChildWidget() core.Widget
ChildWidget returns the child widget.
func (Flexible) CreateRenderObject
func (f Flexible) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
CreateRenderObject creates the renderFlexChild.
func (Flexible) UpdateRenderObject
func (f Flexible) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
UpdateRenderObject updates the renderFlexChild.
type Form
Form is a container widget that groups form fields and provides coordinated validation, save, and reset operations.
Form works with form field widgets that implement the formFieldState interface, such as TextFormField and FormField. These fields automatically register with the nearest ancestor Form when built.
Use FormOf to obtain the FormState from a build context, then call its methods to interact with the form:
- Validate() validates all registered fields and returns true if all pass
- Save() calls OnSaved on all registered fields
- Reset() resets all fields to their initial values
Autovalidate behavior:
- When Autovalidate is true, individual fields validate themselves when their value changes (after user interaction).
- This does NOT validate untouched fields, avoiding premature error display.
- Call Validate() explicitly to validate all fields (e.g., on form submission).
Example:
var formState *widgets.FormState
Form{
Autovalidate: true,
OnChanged: func() {
// Called when any field changes
},
Child: Column{
Children: []core.Widget{
TextFormField{Label: "Email", Validator: validateEmail},
TextFormField{Label: "Password", Obscure: true},
Button{
Child: Text{Content: "Submit"},
OnPressed: func() {
if formState.Validate() {
formState.Save()
}
},
},
},
},
}
type Form struct {
core.StatefulBase
// Child is the form content.
Child core.Widget
// Autovalidate runs validators when fields change.
Autovalidate bool
// OnChanged is called when any field changes.
OnChanged func()
}
Example:
This example shows a form with validation.
package main
import (
"fmt"
"github.com/go-drift/drift/pkg/core"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
form := widgets.Form{
Autovalidate: true,
OnChanged: func() {
fmt.Println("Form changed")
},
Child: widgets.Column{
Children: []core.Widget{
widgets.TextFormField{
Label: "Email",
Validator: func(value string) string {
if value == "" {
return "Email is required"
}
return ""
},
},
widgets.TextFormField{
Label: "Password",
Obscure: true,
Validator: func(value string) string {
if len(value) < 8 {
return "Password must be at least 8 characters"
}
return ""
},
},
},
},
}
_ = form
}
func (Form) CreateState
func (f Form) CreateState() core.State
type FormField
FormField is a generic form field widget for building custom form inputs that integrate with Form for validation, save, and reset operations.
Unlike TextFormField which is specialized for text input, FormField[T] can wrap any input widget type and manage values of any type T.
The Builder function receives the FormFieldState and should return a widget that displays the current value and calls DidChange when the value changes.
Example (custom checkbox field):
FormField[bool]{
InitialValue: false,
Validator: func(checked bool) string {
if !checked {
return "You must accept the terms"
}
return ""
},
Builder: func(state *widgets.FormFieldState[bool]) core.Widget {
return Row{
Children: []core.Widget{
Checkbox{
Value: state.Value(),
OnChanged: func(v bool) { state.DidChange(v) },
},
Text{Content: "I accept the terms"},
if state.HasError() {
Text{Content: state.ErrorText(), Style: errorStyle},
},
},
}
},
OnSaved: func(checked bool) {
// Called when FormState.Save() is invoked
},
}
type FormField[T comparable] struct {
core.StatefulBase
// InitialValue is the field's starting value.
InitialValue T
// Builder renders the field using its state.
Builder func(*FormFieldState[T]) core.Widget
// OnSaved is called when the form is saved.
OnSaved func(T)
// Validator returns an error message or empty string.
Validator func(T) string
// OnChanged is called when the field value changes.
OnChanged func(T)
// Disabled controls whether the field participates in validation.
Disabled bool
// Autovalidate enables validation when the value changes.
Autovalidate bool
}
Example:
This example shows a custom form field with FormField.
package main
import (
"fmt"
"github.com/go-drift/drift/pkg/core"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
formField := widgets.FormField[bool]{
InitialValue: false,
Validator: func(checked bool) string {
if !checked {
return "You must accept the terms"
}
return ""
},
Builder: func(state *widgets.FormFieldState[bool]) core.Widget {
return widgets.Row{
Children: []core.Widget{
widgets.Checkbox{
Value: state.Value(),
OnChanged: func(v bool) { state.DidChange(v) },
},
widgets.HSpace(8),
widgets.Text{Content: "I accept the terms"},
},
}
},
OnSaved: func(checked bool) {
fmt.Printf("Terms accepted: %v\n", checked)
},
}
_ = formField
}
func (FormField[T]) CreateState
func (f FormField[T]) CreateState() core.State
type FormFieldState
FormFieldState stores the mutable state for a FormField and provides methods for querying and updating the field value.
Methods:
- Value() T: Returns the current field value.
- ErrorText() string: Returns the current validation error message, or empty string.
- HasError() bool: Returns true if there is a validation error.
- DidChange(T): Call this when the field value changes to update state and trigger validation.
- Validate() bool: Runs the validator and returns true if valid.
- Save(): Calls the OnSaved callback with the current value.
- Reset(): Resets to InitialValue and clears errors.
type FormFieldState[T comparable] struct {
// contains filtered or unexported fields
}
func (*FormFieldState[T]) Build
func (s *FormFieldState[T]) Build(ctx core.BuildContext) core.Widget
Build renders the field by calling Builder.
func (*FormFieldState[T]) DidChange
func (s *FormFieldState[T]) DidChange(value T)
DidChange updates the value and triggers validation/notifications.
func (*FormFieldState[T]) DidChangeDependencies
func (s *FormFieldState[T]) DidChangeDependencies()
DidChangeDependencies is a no-op for FormFieldState.
func (*FormFieldState[T]) DidUpdateWidget
func (s *FormFieldState[T]) DidUpdateWidget(oldWidget core.StatefulWidget)
DidUpdateWidget updates value if the initial value changed before interaction.
func (*FormFieldState[T]) Dispose
func (s *FormFieldState[T]) Dispose()
Dispose unregisters the field from the form.
func (*FormFieldState[T]) ErrorText
func (s *FormFieldState[T]) ErrorText() string
ErrorText returns the current error message.
func (*FormFieldState[T]) HasError
func (s *FormFieldState[T]) HasError() bool
HasError reports whether the field has an error.
func (*FormFieldState[T]) InitState
func (s *FormFieldState[T]) InitState()
InitState initializes the field value from the widget.
func (*FormFieldState[T]) Reset
func (s *FormFieldState[T]) Reset()
Reset returns the field to its initial value.
func (*FormFieldState[T]) Save
func (s *FormFieldState[T]) Save()
Save triggers the OnSaved callback.
func (*FormFieldState[T]) SetElement
func (s *FormFieldState[T]) SetElement(element *core.StatefulElement)
SetElement stores the element for rebuilds.
func (*FormFieldState[T]) SetState
func (s *FormFieldState[T]) SetState(fn func())
SetState executes fn and schedules rebuild.
func (*FormFieldState[T]) Validate
func (s *FormFieldState[T]) Validate() bool
Validate runs the field validator.
func (*FormFieldState[T]) Value
func (s *FormFieldState[T]) Value() T
Value returns the current value.
type FormState
FormState manages the state of a Form widget and provides methods to interact with all registered form fields.
Obtain a FormState using FormOf from within a build context, or by storing a reference when building the form.
Methods:
- Validate() bool: Validates all fields and returns true if all pass.
- Save(): Calls OnSaved on all fields (typically after successful validation).
- Reset(): Resets all fields to their initial values and clears errors.
FormState tracks a generation counter that increments on validation, reset, and field changes, triggering rebuilds of dependent widgets.
type FormState struct {
core.StateBase
// contains filtered or unexported fields
}
func FormOf
func FormOf(ctx core.BuildContext) *FormState
FormOf returns the FormState of the nearest ancestor Form widget, or nil if there is no Form ancestor.
Form fields like TextFormField use this internally to register with their parent form. You can also use it to obtain the FormState for calling Validate, Save, or Reset.
Example:
func (s *myWidgetState) Build(ctx core.BuildContext) core.Widget {
formState := widgets.FormOf(ctx)
return Button{
Child: Text{Content: "Submit"},
OnPressed: func() {
if formState != nil && formState.Validate() {
formState.Save()
}
},
}
}
func (*FormState) Build
func (s *FormState) Build(ctx core.BuildContext) core.Widget
Build renders the form scope.
func (*FormState) Dispose
func (s *FormState) Dispose()
Dispose clears registrations.
func (*FormState) InitState
func (s *FormState) InitState()
InitState initializes the form state.
func (*FormState) NotifyChanged
func (s *FormState) NotifyChanged()
NotifyChanged informs listeners that a field changed. When autovalidate is enabled, the calling field is expected to validate itself rather than having the form validate all fields (which would show errors on untouched fields). Form.Validate() can still be called explicitly to validate all.
func (*FormState) RegisterField
func (s *FormState) RegisterField(field formFieldState)
RegisterField registers a field with this form.
func (*FormState) Reset
func (s *FormState) Reset()
Reset resets all fields to their initial values.
func (*FormState) Save
func (s *FormState) Save()
Save calls OnSaved for all fields.
func (*FormState) UnregisterField
func (s *FormState) UnregisterField(field formFieldState)
UnregisterField unregisters a field from this form.
func (*FormState) Validate
func (s *FormState) Validate() bool
Validate runs validators on all fields.
type GestureDetector
GestureDetector wraps a child widget with gesture recognition callbacks.
GestureDetector supports multiple gesture types that can be used together:
- Tap: Simple tap/click detection via OnTap
- Pan: Free-form drag in any direction via OnPanStart/Update/End
- Horizontal drag: Constrained horizontal drag via OnHorizontalDrag*
- Vertical drag: Constrained vertical drag via OnVerticalDrag*
Example (tap detection):
GestureDetector{
OnTap: func() { handleTap() },
Child: Container{Color: colors.Blue, Child: icon},
}
Example (draggable widget):
GestureDetector{
OnPanStart: func(d DragStartDetails) { ... },
OnPanUpdate: func(d DragUpdateDetails) { ... },
OnPanEnd: func(d DragEndDetails) { ... },
Child: draggableItem,
}
For simple tap handling on buttons, prefer Button which provides visual feedback. GestureDetector is best for custom gestures.
type GestureDetector struct {
core.RenderObjectBase
Child core.Widget
OnTap func()
OnPanStart func(DragStartDetails)
OnPanUpdate func(DragUpdateDetails)
OnPanEnd func(DragEndDetails)
OnPanCancel func()
OnHorizontalDragStart func(DragStartDetails)
OnHorizontalDragUpdate func(DragUpdateDetails)
OnHorizontalDragEnd func(DragEndDetails)
OnHorizontalDragCancel func()
OnVerticalDragStart func(DragStartDetails)
OnVerticalDragUpdate func(DragUpdateDetails)
OnVerticalDragEnd func(DragEndDetails)
OnVerticalDragCancel func()
}
Example:
This example shows how to handle tap gestures.
package main
import (
"fmt"
"github.com/go-drift/drift/pkg/graphics"
"github.com/go-drift/drift/pkg/layout"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
detector := widgets.GestureDetector{
OnTap: func() {
fmt.Println("Tapped!")
},
Child: widgets.Container{
Color: graphics.RGB(200, 200, 200),
Padding: layout.EdgeInsetsAll(20),
Child: widgets.Text{
Content: "Tap me",
},
},
}
_ = detector
}
Example (Drag):
This example shows drag gesture handling.
package main
import (
"fmt"
"github.com/go-drift/drift/pkg/graphics"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
var offsetX, offsetY float64
detector := widgets.GestureDetector{
OnPanStart: func(details widgets.DragStartDetails) {
fmt.Println("Drag started")
},
OnPanUpdate: func(details widgets.DragUpdateDetails) {
offsetX += details.Delta.X
offsetY += details.Delta.Y
fmt.Printf("Position: (%.0f, %.0f)\n", offsetX, offsetY)
},
OnPanEnd: func(details widgets.DragEndDetails) {
fmt.Printf("Drag ended with velocity: (%.0f, %.0f)\n",
details.Velocity.X, details.Velocity.Y)
},
Child: widgets.Container{
Width: 100,
Height: 100,
Color: graphics.RGB(100, 149, 237),
},
}
_ = detector
}
func Drag
func Drag(onUpdate func(DragUpdateDetails), child core.Widget) GestureDetector
Drag wraps a child with pan (omnidirectional) drag handlers.
func HorizontalDrag
func HorizontalDrag(onUpdate func(DragUpdateDetails), child core.Widget) GestureDetector
HorizontalDrag wraps a child with horizontal-only drag handlers.
func Tap
func Tap(onTap func(), child core.Widget) GestureDetector
Tap wraps a child with a tap handler.
func VerticalDrag
func VerticalDrag(onUpdate func(DragUpdateDetails), child core.Widget) GestureDetector
VerticalDrag wraps a child with vertical-only drag handlers.
func (GestureDetector) ChildWidget
func (g GestureDetector) ChildWidget() core.Widget
func (GestureDetector) CreateRenderObject
func (g GestureDetector) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
func (GestureDetector) UpdateRenderObject
func (g GestureDetector) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
type Icon
Icon renders a single glyph with icon-friendly defaults.
Styling Model
Icon is explicit by default — all visual properties use their struct field values directly. A zero value means zero, not "use theme default." For example:
- Size: 0 means zero size (not rendered)
- Color: 0 means transparent (invisible)
For theme-styled icons, use [theme.IconOf] which sets Size to 24 and Color to the theme's OnSurface color.
Creation Patterns
Struct literal (full control):
widgets.Icon{
Glyph: "★",
Size: 32,
Color: graphics.RGB(255, 193, 7),
}
Themed (reads from current theme):
theme.IconOf(ctx, "✓")
// Pre-filled with standard size (24) and theme color
Icon renders the glyph as a Text widget with MaxLines: 1 and the specified size and color. Use Weight to control font weight if needed.
type Icon struct {
core.StatelessBase
// Glyph is the text glyph to render.
Glyph string
// Size is the font size for the icon. Zero means zero size (not rendered).
Size float64
// Color is the icon color. Zero means transparent.
Color graphics.Color
// Weight sets the font weight if non-zero.
Weight graphics.FontWeight
}
func (Icon) Build
func (i Icon) Build(ctx core.BuildContext) core.Widget
type IgnorePointer
IgnorePointer lays out and paints its child normally but optionally blocks all hit testing. When Ignoring is true, pointer events cannot reach the child or any of its descendants. This is useful for disabling interaction during animations without affecting visual output.
type IgnorePointer struct {
core.RenderObjectBase
// Ignoring controls whether pointer events are blocked.
Ignoring bool
// Child is the widget to render.
Child core.Widget
}
func (IgnorePointer) ChildWidget
func (ip IgnorePointer) ChildWidget() core.Widget
func (IgnorePointer) CreateRenderObject
func (ip IgnorePointer) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
func (IgnorePointer) UpdateRenderObject
func (ip IgnorePointer) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
type Image
Image renders a bitmap image onto the canvas with configurable sizing and scaling.
Image accepts a Go image.Image as its Source. The image is rendered using the specified Fit mode to control scaling behavior.
Creation Pattern
Use struct literal:
widgets.Image{
Source: loadedImage,
Width: 200,
Height: 150,
Fit: widgets.ImageFitCover,
SemanticLabel: "Product photo",
}
Image Fit Modes
- ImageFitContain: Scales to fit within the box while maintaining aspect ratio (default)
- ImageFitFill: Stretches the image to completely fill the box (may distort)
- ImageFitCover: Scales to cover the box while maintaining aspect ratio (may crop)
- ImageFitNone: Uses the image's intrinsic size
- ImageFitScaleDown: Like Contain, but never scales up
For decorative images that don't convey information, set ExcludeFromSemantics to true to hide them from screen readers.
type Image struct {
core.RenderObjectBase
// Source is the image to render.
Source image.Image
// Width overrides the image width if non-zero.
Width float64
// Height overrides the image height if non-zero.
Height float64
// Fit controls how the image is scaled within its bounds.
Fit ImageFit
// Alignment positions the image within its bounds.
Alignment layout.Alignment
// SemanticLabel provides an accessibility description of the image.
SemanticLabel string
// ExcludeFromSemantics excludes the image from the semantics tree when true.
// Use this for decorative images that don't convey meaningful content.
ExcludeFromSemantics bool
}
Example:
This example shows image display with fit modes.
package main
import (
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
// Example with nil source (placeholder)
placeholder := widgets.Image{
Width: 200,
Height: 150,
Fit: widgets.ImageFitCover,
}
_ = placeholder
}
func (Image) CreateRenderObject
func (i Image) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
func (Image) UpdateRenderObject
func (i Image) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
func (Image) WithAlignment
func (i Image) WithAlignment(alignment layout.Alignment) Image
WithAlignment returns a copy of the image with the specified alignment.
func (Image) WithFit
func (i Image) WithFit(fit ImageFit) Image
WithFit returns a copy of the image with the specified fit mode.
func (Image) WithSize
func (i Image) WithSize(width, height float64) Image
WithSize returns a copy of the image with the specified width and height.
type ImageFit
ImageFit controls how an image is scaled within its box.
type ImageFit int
const (
// ImageFitContain scales the image to fit within its bounds.
// This is the zero value, making it the default for [Image].
ImageFitContain ImageFit = iota
// ImageFitFill stretches the image to fill its bounds.
ImageFitFill
// ImageFitCover scales the image to cover its bounds.
ImageFitCover
// ImageFitNone leaves the image at its intrinsic size.
ImageFitNone
// ImageFitScaleDown fits the image if needed, otherwise keeps intrinsic size.
ImageFitScaleDown
)
func (ImageFit) String
func (f ImageFit) String() string
String returns a human-readable representation of the image fit mode.
type IndexedStack
IndexedStack is a Stack that only displays one child at a time.
By default, all children are laid out (maintaining their state), but only the child at Index is painted and receives hit tests. This is useful for tab views or wizards where you want to preserve the state of off-screen pages.
With Fit == StackFitExpand, only the active child is laid out because the stack size is constraint-driven (inactive children cannot affect it).
Example:
IndexedStack{
Index: currentTab,
Children: []core.Widget{
HomeTab{},
SearchTab{},
ProfileTab{},
},
}
If Index is out of bounds, nothing is painted.
type IndexedStack struct {
core.RenderObjectBase
Children []core.Widget
Alignment layout.Alignment
Fit StackFit
Index int
}
Example:
This example shows tab-like switching with IndexedStack.
package main
import (
"github.com/go-drift/drift/pkg/core"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
var currentTab int
indexedStack := widgets.IndexedStack{
Index: currentTab,
Children: []core.Widget{
widgets.Text{Content: "Home Tab Content"},
widgets.Text{Content: "Search Tab Content"},
widgets.Text{Content: "Profile Tab Content"},
},
}
_ = indexedStack
}
func (IndexedStack) ChildrenWidgets
func (s IndexedStack) ChildrenWidgets() []core.Widget
func (IndexedStack) CreateRenderObject
func (s IndexedStack) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
func (IndexedStack) UpdateRenderObject
func (s IndexedStack) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
type InputDecoration
InputDecoration provides styling configuration for input widgets like DatePicker and TimePicker.
type InputDecoration struct {
// LabelText is shown above the input.
LabelText string
// HintText is shown when the input is empty.
HintText string
// HelperText is shown below the input.
HelperText string
// ErrorText replaces HelperText when validation fails.
ErrorText string
// PrefixIcon is shown at the start of the input.
PrefixIcon core.Widget
// SuffixIcon is shown at the end of the input (e.g., calendar icon).
SuffixIcon core.Widget
// ContentPadding is the padding inside the input field.
ContentPadding layout.EdgeInsets
// BorderRadius for the input field.
BorderRadius float64
// BorderColor when not focused.
BorderColor graphics.Color
// FocusedBorderColor when focused.
FocusedBorderColor graphics.Color
// BackgroundColor of the input field.
BackgroundColor graphics.Color
// LabelStyle for the label text.
LabelStyle graphics.TextStyle
// HintStyle for the hint text.
HintStyle graphics.TextStyle
// HelperStyle for the helper/error text.
HelperStyle graphics.TextStyle
// ErrorColor for error text. Zero means transparent (error text not visible).
ErrorColor graphics.Color
}
type LayoutBuilder
LayoutBuilder defers child building to the layout phase, providing the parent's constraints to the builder function. This enables responsive layouts that adapt to available space.
Because Drift's pipeline runs Build before Layout, widgets normally cannot observe constraints. LayoutBuilder bridges this gap by invoking the builder during the render object's PerformLayout, once actual constraints are known.
Example:
LayoutBuilder{
Builder: func(ctx core.BuildContext, constraints layout.Constraints) core.Widget {
if constraints.MaxWidth > 600 {
return wideLayout()
}
return narrowLayout()
},
}
type LayoutBuilder struct {
Builder func(ctx core.BuildContext, constraints layout.Constraints) core.Widget
}
func (LayoutBuilder) CreateElement
func (lb LayoutBuilder) CreateElement() core.Element
CreateElement returns a [core.LayoutBuilderElement] that defers child building to the layout phase.
func (LayoutBuilder) CreateRenderObject
func (lb LayoutBuilder) CreateRenderObject(ctx core.BuildContext) layout.RenderObject
CreateRenderObject creates the backing renderLayoutBuilder, which invokes the layout callback during PerformLayout and lays out the resulting child.
func (LayoutBuilder) Key
func (lb LayoutBuilder) Key() any
Key returns nil. LayoutBuilder does not support keyed identity.
func (LayoutBuilder) LayoutBuilder
func (lb LayoutBuilder) LayoutBuilder() func(ctx core.BuildContext, constraints layout.Constraints) core.Widget
LayoutBuilder returns the builder callback. This satisfies the [core.LayoutBuilderWidget] interface so the element can retrieve the builder during the layout callback.
func (LayoutBuilder) UpdateRenderObject
func (lb LayoutBuilder) UpdateRenderObject(ctx core.BuildContext, renderObject layout.RenderObject)
UpdateRenderObject is a no-op. The render object's only mutable state is the layout callback, which is set directly by the element during Mount.
type LinearProgressIndicator
LinearProgressIndicator displays a linear progress indicator. When Value is nil, it shows an indeterminate animation. When Value is set, it shows determinate progress from 0.0 to 1.0.
Styling Model
LinearProgressIndicator is explicit by default — zero values mean zero (no color). For theme-styled indicators, use [theme.LinearProgressIndicatorOf] which pre-fills Color from Primary and TrackColor from SurfaceVariant.
Creation Patterns
Struct literal (full control):
widgets.LinearProgressIndicator{
Value: nil, // indeterminate
Color: colors.Primary,
TrackColor: colors.SurfaceVariant,
Height: 4,
BorderRadius: 2,
}
Themed (reads from current theme):
theme.LinearProgressIndicatorOf(ctx, nil) // indeterminate
theme.LinearProgressIndicatorOf(ctx, &progress) // determinate
type LinearProgressIndicator struct {
core.StatefulBase
// Value is the progress value (0.0 to 1.0). Nil means indeterminate.
Value *float64
// Color is the indicator color. Zero means transparent (invisible).
Color graphics.Color
// TrackColor is the background track color. Zero means no track.
TrackColor graphics.Color
// Height is the thickness of the indicator. Zero means zero height (not rendered).
Height float64
// BorderRadius is the corner radius. Zero means sharp corners.
BorderRadius float64
// MinWidth is the minimum width for the indicator. Zero uses constraints.
MinWidth float64
}
func (LinearProgressIndicator) CreateState
func (l LinearProgressIndicator) CreateState() core.State
type ListView
ListView displays a scrollable list of widgets.
ListView wraps its children in a ScrollView with either a Row or Column depending on ScrollDirection. All children are built immediately, making it suitable for small lists with a known number of items.
For large lists or dynamic content, use ListViewBuilder which builds children on demand and supports virtualization for better performance.
Example:
ListView{
Padding: layout.EdgeInsetsAll(16),
Children: []core.Widget{
ListTile{Title: "Item 1"},
ListTile{Title: "Item 2"},
ListTile{Title: "Item 3"},
},
}
type ListView struct {
core.StatelessBase
// Children are the widgets to display in the list.
Children []core.Widget
// ScrollDirection is the axis along which the list scrolls. Defaults to vertical.
ScrollDirection Axis
// Controller manages scroll position and provides scroll notifications.
Controller *ScrollController
// Physics determines how the scroll view responds to user input.
Physics ScrollPhysics
// Padding is applied around the list content.
Padding layout.EdgeInsets
// MainAxisAlignment controls how children are positioned along the scroll axis.
MainAxisAlignment MainAxisAlignment
// MainAxisSize determines how much space the list takes along the scroll axis.
MainAxisSize MainAxisSize
}
Example:
This example shows a simple static list.
package main
import (
"github.com/go-drift/drift/pkg/core"
"github.com/go-drift/drift/pkg/layout"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
items := []string{"Apple", "Banana", "Cherry"}
children := make([]core.Widget, len(items))
for i, item := range items {
children[i] = widgets.Container{
Padding: layout.EdgeInsetsSymmetric(16, 12),
Child: widgets.Text{Content: item},
}
}
listView := widgets.ListView{
Children: children,
Padding: layout.EdgeInsetsAll(8),
}
_ = listView
}
func (ListView) Build
func (l ListView) Build(ctx core.BuildContext) core.Widget
type ListViewBuilder
ListViewBuilder builds list items on demand for efficient scrolling of large lists.
Unlike ListView which builds all children upfront, ListViewBuilder only builds widgets for visible items plus a cache region, making it suitable for lists with hundreds or thousands of items.
Virtualization
For virtualization to work, ItemExtent must be set to a fixed height (or width for horizontal lists). This allows the list to calculate which items are visible without building all items. If ItemExtent is 0, all items are built immediately.
CacheExtent controls how many pixels beyond the visible area are pre-built, reducing flicker during fast scrolling.
Example:
ListViewBuilder{
ItemCount: 1000,
ItemExtent: 56, // Fixed height per item enables virtualization
CacheExtent: 200, // Pre-build 200px beyond visible area
ItemBuilder: func(ctx core.BuildContext, index int) core.Widget {
return ListTile{Title: fmt.Sprintf("Item %d", index)}
},
}
type ListViewBuilder struct {
core.StatefulBase
// ItemCount is the total number of items in the list.
ItemCount int
// ItemBuilder creates widgets for visible items. Called with the build context and item index.
ItemBuilder func(ctx core.BuildContext, index int) core.Widget
// ItemExtent is the fixed extent of each item along the scroll axis. Required for virtualization.
ItemExtent float64
// CacheExtent is the number of pixels to render beyond the visible area.
CacheExtent float64
// ScrollDirection is the axis along which the list scrolls. Defaults to vertical.
ScrollDirection Axis
// Controller manages scroll position and provides scroll notifications.
Controller *ScrollController
// Physics determines how the scroll view responds to user input.
Physics ScrollPhysics
// Padding is applied around the list content.
Padding layout.EdgeInsets
// MainAxisAlignment controls how children are positioned along the scroll axis.
MainAxisAlignment MainAxisAlignment
// MainAxisSize determines how much space the list takes along the scroll axis.
MainAxisSize MainAxisSize
}
Example:
This example shows how to create a dynamic list with ListViewBuilder.
package main
import (
"github.com/go-drift/drift/pkg/core"
"github.com/go-drift/drift/pkg/layout"
"github.com/go-drift/drift/pkg/widgets"
)
func main() {
items := []string{"Apple", "Banana", "Cherry", "Date", "Elderberry"}
listView := widgets.ListViewBuilder{
ItemCount: len(items),
ItemExtent: 48,
ItemBuilder: func(ctx core.BuildContext, index int) core.Widget {
return widgets.Container{
Padding: layout.EdgeInsetsSymmetric(16, 12),
Child: widgets.Text{Content: items[index]},
}
},
Padding: layout.EdgeInsetsAll(8),
}
_ = listView
}
func (ListViewBuilder) CreateState
func (l ListViewBuilder) CreateState() core.State
type Lottie
Lottie renders a Lottie animation from a loaded [lottie.Animation].
Loading
The Source field requires a pre-loaded animation. Use [lottie.Load], [lottie.LoadBytes], or [lottie.LoadFile] to obtain one.
Auto\-play Behavior
By default, the animation plays automatically on mount. The Repeat field controls what happens when playback completes: stop, loop, or bounce.
Programmatic Control
Pass a Controller to take full control of playback. When a Controller is provided, the widget does not auto-play and ignores Repeat and OnComplete. The controller's Value (0.0 to 1.0) maps directly to animation progress. Switching between external and self-managed control at runtime is supported.
Sizing Behavior
When Width and/or Height are specified, the animation scales to fit. When both are zero, the widget uses the animation's intrinsic dimensions. When one dimension is specified, the other is calculated from the aspect ratio.