Basic controls
Control catalog · Application contract · Styling inventory
These controls cover text, actions, boolean choices, and single-line input.
Examples use the language fragment contexts.
The C++ context includes xui\application.hpp.
Common properties
Element supplies layout, identity, and explicit style attachments.
Control adds names, enabled state, visibility, focus notifications, help text, and context menus.
Application code normally creates a concrete control, not a base object.
namespace ControlExamples;
component ApplyProperties {
view {
VStack() {
Button("Apply", ref: Apply, id: "apply-preferences",
help: "Apply these preferences to the current document.", enabled: true);
}
}
}
C# sets the minimum size after component construction:
var view = new ControlExamples.ApplyProperties(window);
view.Apply.MinimumSize(100, 36);
var action = window.Button("Apply")
.SetAutomationId("apply-preferences")
.Help("Apply these preferences to the current document.")
.MinimumSize(100, 36)
.SetEnabled(true);
root.Add(action);
let action = window.button("Apply")?;
window.update(&[
Property { text: "apply-preferences", ..Property::new(&action, PropertyKind::AutomationId) },
Property { a: 100.0, b: 36.0, ..Property::new(&action, PropertyKind::MinimumSize) },
Property { integer: 1, ..Property::new(&action, PropertyKind::Enabled) },
])?;
action.help("Apply these preferences to the current document.")?;
root.add(&action, 0.0)?;
auto action = std::make_shared<xui::Button>(L"Apply");
action->set_automation_id(L"apply-preferences");
action->set_help_text(L"Apply these preferences to the current document.");
action->set_minimum_size({100, 36});
action->set_enabled(true);
root->add(action);
set_fixed_size also sets both size limits.
set_preferred_size disables automatic sizing.
Parent constraints still limit the arranged size.
The size contract explains flex allocation and automatic text measurements.
Hidden controls and disabled ancestors restrict descendant interaction.
on_focus reports focus but does not request it.
Use window.focus(*action) from an active-window callback.
The framework rejects foreign, closed, or disabled focus targets.
Label
Use Label for noneditable text.
Use TextInput or MultilineText for user edits.
A label can use a semantic tone, heading typography, or explicit wrapping.
These styles supply heading typography and wrapping. The C# binding has no semantic heading or tone setter.
namespace ControlExamples;
component WorkspaceLabels {
style Title for Label {
part label { fontSize: 24; fontWeight: 600; }
}
style Description for Label {
part label { wrapping: true; maximumLines: 3; }
}
view {
VStack() {
Text("Workspace preferences", style: Title);
Text("Choose the preferences for this workspace.", style: Description);
}
}
}
The binding uses explicit typography instead of the C++ heading and tone setters.
var title = window.Label("Workspace preferences");
title.SetControlStyleValues(StylePart.Label, new PartStyleValues {
FontSize = 24, FontWeight = 600
});
var description = window.Label("Choose the preferences for this workspace.");
description.SetControlStyleValues(StylePart.Label, new PartStyleValues {
Wrapping = true, MaximumLines = 3
});
root.Add(title).Add(description);
The binding uses explicit typography instead of the C++ heading and tone setters.
let title = window.label("Workspace preferences")?;
title.set_control_style_values(StylePart::Label, PartStyleValues {
font_size: Some(24.0), font_weight: Some(600), ..Default::default()
})?;
let description = window.label("Choose the preferences for this workspace.")?;
description.set_control_style_values(StylePart::Label, PartStyleValues {
wrapping: Some(true), maximum_lines: Some(3), ..Default::default()
})?;
root.add(&title, 0.0)?;
root.add(&description, 0.0)?;
auto title = std::make_shared<xui::Label>(L"Workspace preferences");
title->set_heading(true);
auto description = std::make_shared<xui::Label>(
L"Choose the preferences for this workspace.");
description->set_tone(xui::TextTone::secondary);
description->set_wrapping(true, 3);
root->add(title);
root->add(description);
set_text updates the visible text and accessible name.
set_caption, set_subtitle, and set_body_strong select other text presentations.
Labels do not expose an action callback or an extra keyboard focus stop.
The style target is label.
The active text part is label, caption, or heading, according to text_style().
Semantic tones supply defaults below authored colors.
Wrapping limits do not shorten the accessible name.
Button
Use a Button for an action.
Use Toggle for a labeled checkbox.
Use ToggleButton for a button-shaped toggle action.
Use SplitButton for independent primary and secondary actions.
namespace ControlExamples;
component ApplyAction {
state string Message = "Ready";
view {
VStack() {
Text(Message);
Button("Apply preferences", click: Apply);
}
}
code csharp {
void Apply() => Message = "Preferences applied";
}
}
var message = window.Label("Ready");
var apply = window.Button("Apply preferences");
apply.Click += () => message.Text = "Preferences applied";
root.Add(message).Add(apply);
let message = window.label("Ready")?;
let apply = window.button("Apply preferences")?;
let output = message.downgrade();
apply.on_event(move |event| {
let Some(output) = output.upgrade() else { return Ok(()); };
if event.kind == 1 {
output.set_text("Preferences applied").map_err(|error| {
eprintln!("Apply callback: {error}");
error
})?;
}
Ok(())
})?;
root.add(&message, 0.0)?;
root.add(&apply, 0.0)?;
auto message = std::make_shared<xui::Label>(L"Ready");
auto apply = std::make_shared<xui::Button>(L"Apply preferences");
apply->on_click([message] { message->set_text(L"Preferences applied"); });
root->add(message);
root->add(apply);
Button variants use the same constructor:
C# configures behavior through the generated references. The binding has no silent checked-state setter for Button, so the Pin button starts unchecked.
namespace ControlExamples;
component ButtonVariants {
view {
VStack() {
Button("Increase", ref: Repeat);
Button("Pin panel", ref: Pin);
Button("More actions", ref: More, icon: global::Xui.ButtonIcon.More);
}
}
}
var view = new ControlExamples.ButtonVariants(window);
view.Repeat.Behavior(ButtonBehavior.Repeat).RepeatTiming(400, 80);
view.Pin.Behavior(ButtonBehavior.Toggle);
view.More.Behavior(ButtonBehavior.Dropdown);
The binding has no silent checked-state setter for Button, so the Pin button starts unchecked.
var repeat = window.Button("Increase")
.Behavior(ButtonBehavior.Repeat).RepeatTiming(400, 80);
var pin = window.Button("Pin panel").Behavior(ButtonBehavior.Toggle);
var more = window.Button("More actions")
.Behavior(ButtonBehavior.Dropdown).SetIcon(ButtonIcon.More);
root.Add(repeat).Add(pin).Add(more);
Rust exposes the behaviors and set_icon.
It has no silent checked-state setter for Button, so the Pin button starts unchecked.
let repeat = window.button("Increase")?;
repeat.behavior(ButtonBehavior::Repeat)?;
repeat.repeat_timing(400, 80)?;
let pin = window.button("Pin panel")?;
pin.behavior(ButtonBehavior::Toggle)?;
let more = window.button("More actions")?;
more.behavior(ButtonBehavior::Dropdown)?;
root.add(&repeat, 0.0)?;
root.add(&pin, 0.0)?;
root.add(&more, 0.0)?;
auto repeat = std::make_shared<xui::Button>(L"Increase");
repeat->set_behavior(xui::ButtonBehavior::repeat);
repeat->set_repeat_timing(400, 80);
auto pin = std::make_shared<xui::Button>(L"Pin panel");
pin->set_behavior(xui::ButtonBehavior::toggle);
pin->set_checked(true);
auto more = std::make_shared<xui::Button>(L"More actions");
more->set_behavior(xui::ButtonBehavior::dropdown);
more->set_icon(xui::ButtonIcon::more);
root->add(repeat);
root->add(pin);
root->add(more);
on_click handles the command.
on_toggle reports accepted toggle-action changes.
set_checked changes the property without the action callback.
A dropdown appearance does not create a menu. Connect it to Popup or CommandSurface.
Icon-only buttons retain their name for accessibility.
ButtonIcon.Pin and ButtonIcon.PinFilled provide matching outlined and filled pushpins
(pin/pin_filled in C++, Pin/PinFilled in Rust, C icon IDs 33/34).
Pointer capture loss, drag-out release, and window deactivation cancel pending presses.
Enter activates a focused button. Space activates it on release.
For a narrow edge tab, Button("Properties", verticalText: true, size: (32, 120));
rotates the full label 90 degrees clockwise. The reactive verticalText argument defaults to false.
C# exposes VerticalText/SetVerticalText, C++ and Rust expose vertical_text/set_vertical_text,
and C exposes the Button-only boolean XUI_F_VERTICAL_TEXT.
Automatic sizing swaps text dimensions; explicit sizes and authored padding remain in physical axes.
Icons and dropdown arrows stay upright. The full accessible name, hit area, and keyboard activation are unchanged.
The style target is button, with text, icon, and dropdown presentation parts.
Use ControlStyle and PartStyleValues through the common Element attachment API.
Use the shared style contract for precedence and compatibility.
ToggleButton
Use ToggleButton for an action that retains an on/off state, such as a pinned panel.
The native class derives from Button and selects toggle behavior by default.
It does not change an ordinary Button into a checkbox.
namespace ControlExamples;
component PinAction {
state bool Pinned = false;
view {
VStack() {
ToggleButton("Pin panel", checked: Pinned, change: ChangePin);
Text(Pinned ? "Panel pinned" : "Panel unpinned");
}
}
code csharp {
void ChangePin(bool value) => Pinned = value;
}
}
var status = window.Label("Panel unpinned");
var pin = window.ToggleButton("Pin panel").SetChecked(false);
pin.Toggled += value => status.Text = value ? "Panel pinned" : "Panel unpinned";
root.Add(pin).Add(status);
let status = window.label("Panel unpinned")?;
let pin = window.toggle_button("Pin panel")?;
pin.set_checked(false)?;
let output = status.downgrade();
pin.on_event(move |event| {
let Some(output) = output.upgrade() else { return Ok(()); };
if event.kind == 2 {
output.set_text(if event.value != 0 { "Panel pinned" } else { "Panel unpinned" })?;
}
Ok(())
})?;
root.add(&pin, 0.0)?;
root.add(&status, 0.0)?;
auto status = std::make_shared<xui::Label>(L"Panel unpinned");
auto pin = std::make_shared<xui::ToggleButton>(L"Pin panel");
pin->set_checked(false);
pin->on_toggle([status](bool value) {
status->set_text(value ? L"Panel pinned" : L"Panel unpinned");
});
root->add(pin);
root->add(status);
checked() reads the native value. set_checked changes it without an action callback.
on_toggle reports accepted toggle actions.
C# uses Checked, SetChecked, and Toggled.
The .xui change argument connects a method with one bool parameter.
Toggle actions do not also call on_click.
The .xui ToggleButton node has no click argument.
Rust uses checked, set_checked, and change event kind 2.
The button style target supplies the checked appearance.
Native Button keyboard, cancellation, enabled-state, and accessibility rules remain in effect.
There is no separate ToggleButton style target.
HyperlinkButton
Use HyperlinkButton for a link-shaped action.
Its native class derives from Button.
Activation calls an application callback. The control does not open a URI or browser.
namespace ControlExamples;
component HelpLink {
state string Message = "Help is closed";
view {
VStack() {
HyperlinkButton("Show help", click: ShowHelp);
Text(Message);
}
}
code csharp {
void ShowHelp() => Message = "Help requested";
}
}
var status = window.Label("Help is closed");
var help = window.HyperlinkButton("Show help");
help.Click += () => status.Text = "Help requested";
root.Add(help).Add(status);
let status = window.label("Help is closed")?;
let help = window.hyperlink_button("Show help")?;
let output = status.downgrade();
help.on_click(move || {
if let Some(output) = output.upgrade() {
output.set_text("Help requested")?;
}
Ok(())
})?;
root.add(&help, 0.0)?;
root.add(&status, 0.0)?;
auto status = std::make_shared<xui::Label>(L"Help is closed");
auto help = std::make_shared<xui::HyperlinkButton>(L"Show help");
help->on_click([status] { status->set_text(L"Help requested"); });
root->add(help);
root->add(status);
C# uses Click, while Rust uses on_click.
Both wrappers expose Invoke or invoke for semantic activation.
Applications own any navigation, URI policy, and error handling.
This control does not infer navigation from its label.
It retains the Button style target and Button input cancellation rules. Its text is underlined, and keyboard focus remains visible. UIA exposes a Hyperlink control with an Invoke action. Enter and Space activate the link. An enabled link uses a hand cursor.
Toggle
Use Toggle for a boolean preference with a checkbox.
Its accessible role and action describe checked state.
namespace ControlExamples;
component PreviewPreference {
state bool Preview = false;
view {
VStack() {
Toggle("Show preview", checked: Preview, change: ChangePreview);
Text(Preview ? "Preview enabled" : "Preview disabled");
}
}
code csharp {
void ChangePreview(bool value) => Preview = value;
}
}
var status = window.Label("Preview disabled");
var preview = window.Toggle("Show preview").SetChecked(false);
preview.Changed += value => status.Text = value ? "Preview enabled" : "Preview disabled";
root.Add(preview).Add(status);
let status = window.label("Preview disabled")?;
let preview = window.toggle("Show preview")?;
preview.checked(false)?;
let output = status.downgrade();
preview.on_event(move |event| {
let Some(output) = output.upgrade() else { return Ok(()); };
if event.kind == 2 {
output.set_text(if event.value != 0 { "Preview enabled" } else { "Preview disabled" })
.map_err(|error| {
eprintln!("Preview callback: {error}");
error
})?;
}
Ok(())
})?;
root.add(&preview, 0.0)?;
root.add(&status, 0.0)?;
auto status = std::make_shared<xui::Label>(L"Preview disabled");
auto preview = std::make_shared<xui::Toggle>(L"Show preview");
preview->set_checked(false);
preview->on_change([status](bool value) {
status->set_text(value ? L"Preview enabled" : L"Preview disabled");
});
root->add(preview);
root->add(status);
checked() reads the value.
set_checked is silent.
Space changes the focused checkbox on release. Enter does not change it.
The style target is toggle.
The label, indicator, and check mark are parts of one control, not independently focusable children.
The checked state follows the value.
ToggleSwitch
Use ToggleSwitch for an on/off preference with a switch pill and thumb.
The native class derives from Toggle.
The existing Toggle control remains a checkbox.
namespace ControlExamples;
component PreviewSwitch {
state bool Preview = false;
view {
VStack() {
ToggleSwitch("Show preview", checked: Preview, change: ChangePreview);
Text(Preview ? "Preview enabled" : "Preview disabled");
}
}
code csharp {
void ChangePreview(bool value) => Preview = value;
}
}
var status = window.Label("Preview disabled");
var preview = window.ToggleSwitch("Show preview").SetChecked(false);
preview.Changed += value => status.Text = value ? "Preview enabled" : "Preview disabled";
root.Add(preview).Add(status);
let status = window.label("Preview disabled")?;
let preview = window.toggle_switch("Show preview")?;
preview.set_checked(false)?;
let output = status.downgrade();
preview.on_event(move |event| {
let Some(output) = output.upgrade() else { return Ok(()); };
if event.kind == 2 {
output.set_text(if event.value != 0 { "Preview enabled" } else { "Preview disabled" })?;
}
Ok(())
})?;
root.add(&preview, 0.0)?;
root.add(&status, 0.0)?;
auto status = std::make_shared<xui::Label>(L"Preview disabled");
auto preview = std::make_shared<xui::ToggleSwitch>(L"Show preview");
preview->set_checked(false);
preview->on_change([status](bool value) {
status->set_text(value ? L"Preview enabled" : L"Preview disabled");
});
root->add(preview);
root->add(status);
The native API retains checked(), silent set_checked, and on_change.
C# uses Checked, SetChecked, and Changed.
Rust uses checked, set_checked, and change event kind 2.
The switch retains the Toggle accessibility role and keyboard behavior.
It does not claim a separate WinUI switch accessibility contract.
The style target remains toggle.
Existing indicator and mark parts supply the pill and thumb presentation.
The indicator size sets the pill height. The pill width is twice that height.
Mark styles apply to the thumb.
The label and switch form one input and accessibility target.
CheckBox
Use CheckBox for an unchecked, checked, or mixed value.
Use the existing Toggle or ToggleSwitch for a binary preference.
CheckBox derives from Toggle, but its on_change callback receives a CheckState, not a boolean.
namespace ControlExamples;
component MixedPreference {
state global::Xui.CheckState Selection = global::Xui.CheckState.Indeterminate;
view {
VStack() {
CheckBox("Include child items", checkState: Selection,
threeState: true, change: ChangeSelection);
Text(Selection.ToString());
}
}
code csharp {
void ChangeSelection(global::Xui.CheckState value) => Selection = value;
}
}
var status = window.Label("Indeterminate");
var choice = window.CheckBox("Include child items")
.SetThreeState(true).SetState(CheckState.Indeterminate);
choice.Changed += value => status.Text = value.ToString();
root.Add(choice).Add(status);
let status = window.label("Indeterminate")?;
let choice = window.check_box("Include child items")?;
choice.set_three_state(true)?;
choice.set_state(CheckState::Indeterminate)?;
let output = status.downgrade();
choice.on_change(move |value| {
if let Some(output) = output.upgrade() {
output.set_text(&format!("{value:?}"))?;
}
Ok(())
})?;
root.add(&choice, 0.0)?;
root.add(&status, 0.0)?;
auto status = std::make_shared<xui::Label>(L"Mixed");
auto choice = std::make_shared<xui::CheckBox>(L"Include child items");
choice->set_three_state(true);
choice->set_state(xui::CheckState::indeterminate);
choice->on_change([status](xui::CheckState value) {
status->set_text(value == xui::CheckState::indeterminate ? L"Mixed" :
value == xui::CheckState::checked ? L"Checked" : L"Unchecked");
});
root->add(choice);
root->add(status);
The default state is unchecked, and three-state input is off.
set_state accepts all three states without an action callback.
set_three_state controls the input cycle, not the allowed property values.
Three-state input cycles from unchecked to checked, then indeterminate, then unchecked.
Two-state input cycles between unchecked and checked. Activation from indeterminate selects unchecked.
Toggle and ToggleSwitch retain their binary state and boolean callbacks.
CheckBox uses the existing toggle style target.
The mark part draws a check or mixed-state dash.
Checked and mixed states use the existing checked style rule.
UIA exposes CheckBox Toggle state, including Indeterminate. Space activates the checkbox. Enter does not change its state.
InfoBadge
Use InfoBadge for a noninteractive dot, count, or icon.
The accessible name describes what the badge means.
The badge does not add an action callback or keyboard focus stop.
namespace ControlExamples;
component NotificationBadges {
view {
HStack() {
InfoBadge("Unread activity");
InfoBadge("Unread messages", count: 12u);
InfoBadge("New items available", icon: global::Xui.ButtonIcon.Add);
}
}
}
var activity = window.InfoBadge("Unread activity");
var unread = window.InfoBadge("Unread messages").SetCount(12);
var available = window.InfoBadge("New items available").SetIcon(ButtonIcon.Add);
root.Add(activity).Add(unread).Add(available);
let activity = window.info_badge("Unread activity")?;
let unread = window.info_badge("Unread messages")?;
unread.set_count(12)?;
let available = window.info_badge("New items available")?;
available.set_icon(ButtonIcon::Add)?;
root.add(&activity, 0.0)?;
root.add(&unread, 0.0)?;
root.add(&available, 0.0)?;
auto activity = std::make_shared<xui::InfoBadge>(L"Unread activity");
auto unread = std::make_shared<xui::InfoBadge>(L"Unread messages");
unread->set_count(12);
auto available = std::make_shared<xui::InfoBadge>(L"New items available");
available->set_icon(xui::ButtonIcon::add);
root->add(activity);
root->add(unread);
root->add(available);
The default kind is dot.
set_count and set_icon select their corresponding kinds. set_dot restores the dot.
Counts use an unsigned 32-bit value.
The visible count uses 99+ for values greater than 99. The stored count remains unchanged.
The accessible name includes the full count, not the shortened visible text.
The .xui node accepts either count or icon, not both.
Without either argument, the node keeps the dot presentation.
InfoBadge uses the inline_status style target with root, message, and icon parts.
The message part styles the count text. There is no title part.
The badge does not create InlineStatus action or dismiss Buttons.
It exposes status live-region semantics, not an interactive input pattern.
TextInput
Use TextInput for one line of ordinary text.
Use PasswordInput for secrets, NumericInput for numbers, and native document controls for multiple paragraphs.
The binding has no search-style, maximum-length, or semantic tone setter for TextInput.
This example reports acceptance through text.
namespace ControlExamples;
component DocumentSearch {
state string Result = "No search text";
view {
VStack() {
TextInput("Search documents", placeholder: "Type a document name",
change: ChangeQuery, submit: SubmitQuery);
Text(Result);
}
}
code csharp {
void ChangeQuery(string text) => Result = text.Length == 0 ? "No search text" : text;
void SubmitQuery() => Result = "Accepted: " + Result;
}
}
The binding has no search-style, maximum-length, or semantic tone setter for this example.
var query = window.TextInput("Search documents").SetPlaceholder("Type a document name");
var result = window.Label("No search text");
query.Changed += text => result.Text = text.Length == 0 ? "No search text" : text;
query.Submitted += () => result.Text = "Accepted: " + result.Text;
root.Add(query).Add(result);
Rust has no placeholder, search-style, maximum-length, or semantic tone setter for this example. The native input still reports committed text and submission.
let query = window.text_input("Search documents")?;
let result = window.label("No search text")?;
let input = query.downgrade();
let output = result.downgrade();
query.on_event(move |event| {
let (Some(input), Some(output)) = (input.upgrade(), output.upgrade()) else { return Ok(()); };
let update = || -> xui::Result<()> {
if event.kind == 2 {
let text = input.text()?;
output.set_text(if text.is_empty() { "No search text" } else { &text })?;
} else if event.kind == 3 {
output.set_text(&format!("Accepted: {}", output.text()?))?;
}
Ok(())
};
update().map_err(|error| {
eprintln!("Search callback: {error}");
error
})
})?;
root.add(&query, 0.0)?;
root.add(&result, 0.0)?;
auto query = std::make_shared<xui::TextInput>(L"Search documents");
query->set_search_style(true);
query->set_placeholder(L"Type a document name");
query->set_maximum_length(256);
auto result = std::make_shared<xui::Label>(L"No search text");
query->on_change([result](const std::wstring& text) {
result->set_text(text.empty() ? L"No search text" : text);
});
query->on_submit([result] { result->set_tone(xui::TextTone::accent); });
root->add(query);
root->add(result);
name() is the accessible field name. text() is the value.
set_selection uses UTF-16 endpoints and does not split surrogate pairs.
set_text does not synthesize a user edit.
on_change receives committed native text, not IME preedit text.
on_submit handles acceptance.
set_shortcut_hint shows a hint but does not register a keyboard shortcut.
set_suggestions connects a SuggestionSource.
The input contract describes asynchronous suggestions and native key ownership.
The style target is text_input.
It supports owned frame parts and supported native text colors and fonts.
The placeholder, search icon, and shortcut badge do not replace the native editor.
The empty and focused states follow the real field.
NativeEditBridge
NativeEditBridge is the public Windows EDIT integration boundary in xui\native_edit.hpp.
Use TextInput for ordinary XUI applications.
The bridge serves code that already owns native parent-window integration.
This complete C++ helper belongs at file scope. It requires a live parent HWND on the calling UI thread:
Unavailable. NativeEditBridge is not an Element and has no C# factory.
The Content node cannot embed this native integration helper.
Use the TextInput example for application markup.
Unavailable. The binding has no NativeEditBridge factory or native parent-message integration API.
Use Window.TextInput for an ordinary native text field.
Unavailable. The binding has no NativeEditBridge factory or native parent-message integration API.
Use Window::text_input for an ordinary native text field.
std::unique_ptr<xui::NativeEditBridge> attach_native_editor(HWND parent) {
auto editor = std::make_unique<xui::NativeEditBridge>();
editor->attach(parent, 1001);
editor->set_placeholder(L"Filter");
editor->set_model_text(L"");
editor->arrange({0, 0, 320, 40});
return editor;
}
Retain the returned bridge for the native editor lifetime.
Its destructor releases the child window and owned font.
attach rejects a second attachment.
Native host code remains responsible for parent message routing, DPI, and lifetime.
selection and set_selection require the live UI thread.
Selection changes reject active IME composition.
Windows retains text, IME, undo, clipboard, and native accessibility. This bridge has no generic style target and no C# or Rust factory. Font, color, inset, and suggestion methods belong to backend integration. They do not replace the normal TextInput property API. The application contract describes this native text boundary.
Next steps
- Arrange the controls.
- Add numeric and choice fields.
- Add help text and context menus.
- Use C# or Rust.