lotusui the ikaito design language for Gio — desktop, mobile, web
GitHub

Input

The single-line input: three variants, seven sizes, composable everything else.

A single-line editor in themed chrome. Structure (labels, buttons, adornments) is COMPOSITION — nil-able Start/End slots and the Field wrapper; behavior (filters, transforms) is caller-owned functions. A file input is a native picker on the web — out of scope for a canvas UI, use your platform's file dialog ("from the web").

Installation

Use the module import (the default), or own the source: lotusui add vendors the component into your app and lotusui update keeps the copy mergeable — see Registry.

Go
go get github.com/ikaito-com/lotusui

# or vendor the source into your app:
go run github.com/ikaito-com/lotusui/cmd/lotusui add input

Usage

Go
var name lotusui.Input
name.LayoutField(th, gtx, "Email")

Disabled

Read-only, dimmed, no caret.

Go
name.Disabled = true

With label

Wrap any control in Field for the label.

Go
lotusui.Field(th, lotusui.FieldOpts{Label: "Email"}, func(gtx C) D {
	return email.LayoutField(th, gtx, "you@example.com")
})

With button

An input beside a button is ordinary composition — a Flex row.

Go
layout.Flex{Alignment: layout.Middle}.Layout(gtx,
	layout.Flexed(1, func(gtx C) D { return email.LayoutField(th, gtx, "you@example.com") }),
	layout.Rigid(lotusui.HSpacer(th.Space.SM)),
	layout.Rigid(lotusui.Button(th, &sub, "Subscribe", lotusui.ButtonOpts{Variant: lotusui.ButtonOutline})),
)

Variants

InputOutline (default), InputSubtle (filled, border on focus), InputFlushed (underline only).

Go
in.Variant = lotusui.InputSubtle
in.Variant = lotusui.InputFlushed

Sizes

All seven shared Size presets — text and padding move together.

Go
in.Size = lotusui.Size2XS // … through lotusui.Size2XL

Start and End elements

Widgets INSIDE the field, beside the editor — an icon, a clear ✕, a visibility eye. nil costs nothing.

Go
search.Start = lotusui.SVGIcon(lotusui.IconEye, 16, th.Palette.FgSubtle)
secret.End = lotusui.SVGIconButtonTint(th, &eyeBtn, lotusui.IconEyeOff, 16, false, th.Palette.FgSubtle)

Suffix addon

A fixed suffix rendered as part of the field's chrome.

Go
sub.LayoutSuffix(th, gtx, "Subdomain", "yourname", ".example.com")

Invalid

Error switches the chrome to danger and renders the message.

Go
in.Error = "this name is already taken"

Field — label, helper, required

The full wrapper is its own component — see Field for helper text, required markers and error slots around any control.

Go
lotusui.Field(th, lotusui.FieldOpts{
	Label: "Email", Helper: "We'll never share it.", Required: true,
}, control)

Filter and Transform

Generic mechanisms for the app's rules: Filter is an allow-list, Transform a same-frame rewrite (here: fold to lowercase).

Go
in.Filter = "abcdefghijklmnopqrstuvwxyz0123456789-"
in.Transform = strings.ToLower

Clear button — composed

A clear button is just an End widget; the core knows nothing about it.

Go
if clearBtn.Clicked(gtx) { in.Editor.SetText("") }
in.End = lotusui.SVGIconButtonTint(th, &clearBtn, lotusui.IconRemove, 14, false, th.Palette.FgSubtle)

Character counter — composed

Cap length with Transform; the counter is Field.Helper, recomputed each frame.

Go
n := len(bio.Editor.Text())
lotusui.Field(th, lotusui.FieldOpts{Label: "Bio", Helper: fmt.Sprintf("%d / 80", n)}, control)

Grid

Inputs side by side are a Flex row — each field takes an equal share.

Go
layout.Flex{}.Layout(gtx,
	layout.Flexed(1, firstNameField),
	layout.Rigid(lotusui.HSpacer(th.Space.MD)),
	layout.Flexed(1, lastNameField),
)

With badge

The label row is yours to compose — here a Beta badge beside the label.

Go
lotusui.HStack(th.Space.SM,
	lotusui.SectionLabel(th, "API token"),
	lotusui.Badge(th, "Beta", lotusui.BadgeOpts{Variant: lotusui.BadgeSecondary, Size: lotusui.SizeXS}),
)

Field group

Stacked Fields in a Card read as one form section.

Go
lotusui.Card(th, lotusui.CardOpts{}, lotusui.VStack(th.Space.MD,
	cityField, postalField,
))

Text addons

Muted text segments in the frame — a currency pair, a URL scheme and TLD — are Start/End widgets.

Go
amount.Start = addonText(th, "$")
amount.End = addonText(th, "USD")

Kbd

Kbd renders the keyboard-cap hint — compose it as the End slot.

Go
search.Start = lotusui.SVGIcon(lotusui.IconSearch, 16, th.Palette.FgSubtle)
search.End = lotusui.Kbd(th, "⌘K")

Spinner

Go
field.End = lotusui.Spinner(th, 14)

Inline button

A small icon button inside the frame — the copy-URL field.

Go
url.End = lotusui.SVGIconButtonTint(th, &copyBtn, lotusui.IconFile, 14, false, th.Palette.FgSubtle)

Block addons

Top/Bottom render full-width rows INSIDE the frame, above/below the editor line.

Go
name.Top = headerRow // a caption, a status row with actions

API

OptionTypeDescription
VariantInputVariantInputOutline (default), InputSubtle, InputFlushed.
SizeSizeThe shared size presets (Size2XS–Size2XL).
FilterstringAllow-list of runes; empty accepts everything.
Transformfunc(string) stringSame-frame rewrite — folds, caps, masks.
ErrorstringDanger chrome + message below the field.
DisabledboolRead-only, dimmed, no caret.
Start / Endlayout.WidgetWidgets inside the field, beside the editor; nil costs nothing.
Top / Bottomlayout.WidgetFull-width rows inside the frame — the block addons.
Kbd(th, key)widgetThe keyboard-cap hint, for End slots and prose.

Every demo on this site is the real Go component compiled to WebAssembly — one gallery app, one bundle, addressed by URL hash.