Buttons
Back at the Catalog, you saw the full lineup. Let's start with the biggest family of all — six controls that all boil down to "clickable," just with different state and presentation.
Every control on this page delegates its rendering to a Behavior<Button>. api.NativeTheme/> and BasicTheme both provide one — see Theming if you haven't met that yet.
Which one do you actually want?
| If you need to... | Reach for |
|---|---|
| Fire an action, with nothing to remember afterward | PushButton |
| Track a plain on/off state | ToggleButton, CheckBox, or Switch — pick by presentation, they're interchangeable underneath |
| Force "exactly one of these" across a group | RadioButton + ButtonGroup |
| Open a URL | HyperLink |
The rest of this page fills in the details behind each row.
PushButton
The plain one: triggers an action when pressed, with the pointer or the keyboard. No state to track beyond the press itself — under the hood, it's just listening for the same clicked event you wired up by hand back on User Input.
- Demo
- Usage
package controls
import io.nacular.doodle.controls.buttons.PushButton
//sampleStart
val button = PushButton("BUTTON").apply {
fired += {
println("Hey! That Hurt!")
}
}
//sampleEndToggleButton
Flips between two states each time it's pressed. Think of it as PushButton with memory.
- Demo
- Usage
package controls
import io.nacular.doodle.controls.buttons.ToggleButton
//sampleStart
val toggleButton = ToggleButton("BUTTON").apply {
selectedChanged += { _,_,selected ->
println("Selected: $selected")
}
}
//sampleEndToggleButton, CheckBox, and Switch (below) are the same control underneath, just with different Behaviors. If none of the built-in looks fit, a custom Behavior is more likely what you want than a fourth control.
CheckBox
An on/off toggle with the presentation everyone already recognizes — a box that fills in when checked.
- Demo
- Usage
package controls
import io.nacular.doodle.controls.buttons.CheckBox
//sampleStart
val checkbox = CheckBox("CHECKBOX").apply {
selectedChanged += { _,_,new ->
println("Checkbox selected: $new, indeterminate: $indeterminate")
}
}
//sampleEndRadioButton
Also, an on/off toggle, but meant for a different job: picking exactly one option from a set. Group several together with ButtonGroup, and selecting one clears the rest.
- Demo
- Usage
package controls
import io.nacular.doodle.controls.buttons.RadioButton
//sampleStart
val radioButton = RadioButton("RADIO BUTTON").apply {
selectedChanged += { _,_,new ->
println("Radio selected: $new")
}
}
//sampleEndSwitch
Same on/off idea as CheckBox, dressed as a sliding toggle instead. Under the hood, it's just a ToggleButton — BasicTheme styles it via basicSwitchBehavior.
- Demo
- Usage
package controls
import io.nacular.doodle.controls.buttons.Switch
import io.nacular.doodle.geometry.Size
//sampleStart
val switch = Switch().apply {
suggestSize(Size(50, 30))
selectedChanged += { _,_,new ->
println("Switch selected: $new")
}
}
//sampleEndThat sliding motion is exactly the kind of thing by animate(...) from Animation is built for, if you're writing a custom Behavior for one of these and want the state change to glide rather than snap.
HyperLink
The odd one out in a good way — this one opens a URL rather than tracking state.
- Demo
- Usage
It's fully customizable too: NativeHyperLinkStyler lets you supply your own Behavior while keeping the built-in link opening functionality intact.
- Demo
- Usage
package hyperlink
import io.nacular.doodle.controls.buttons.Button
import io.nacular.doodle.controls.buttons.HyperLink
import io.nacular.doodle.controls.theme.CommonTextButtonBehavior
import io.nacular.doodle.core.Behavior
import io.nacular.doodle.core.Icon
import io.nacular.doodle.drawing.Canvas
import io.nacular.doodle.drawing.Color.Companion.Red
import io.nacular.doodle.drawing.TextMetrics
import io.nacular.doodle.geometry.Size
import io.nacular.doodle.text.TextDecoration.Companion.UnderLine
import io.nacular.doodle.text.invoke
import io.nacular.doodle.theme.native.NativeHyperLinkStyler
import kotlin.math.max
fun example(linkStyler: NativeHyperLinkStyler, textMetrics: TextMetrics, searchIcon: Icon<Button>) {
//sampleStart
val styledHyperLink = HyperLink(url = "https://www.google.com", text = "Google Search").apply {
val textSize = textMetrics.size(text, font)
val iconSize = searchIcon.size(this)
this.suggestSize(Size(textSize.width + iconTextSpacing + iconSize.width, max(textSize.height, iconSize.height)))
this.icon = searchIcon
this.font = font
this.acceptsThemes = false
this.behavior = linkStyler(this, object: CommonTextButtonBehavior<HyperLink>(textMetrics) {
override fun render(view: HyperLink, canvas: Canvas) {
// Custom text styling, with underline on pointer-over
val styledText = Red { font { text } }.let {
if (view.model.pointerOver) UnderLine { it } else it
}
canvas.text(styledText, at = textPosition(view))
searchIcon.render(view, canvas, iconPosition(view, text, searchIcon))
}
}) as Behavior<Button>
}
//sampleEnd
}NativeHyperLinkStyler is available whenever the nativeHyperLinkBehavior (Web, Desktop) module is included.
Recap
PushButtonandHyperLinkdon't track state — they just fire on press.ToggleButton,CheckBox, andSwitchall track a two-state on/off value — pick whichever presentation fits your design, since they're functionally interchangeable.RadioButtonis the one built for mutual exclusion — pair it withButtonGroupwhen you need "pick exactly one."- All six render through
Behavior<Button>, so aThemestyles all of them consistently at once.
Next: on to Sliders & Value Pickers for continuous and ranged input.