Repository navigation
Buttons Inputs
Blinc provides ready-to-use input widgets. Their hover, press and checked looks are patched in place from signals; they don't rebuild when the pointer moves.
use blinc_layout::prelude::*;
button("Save").on_click(|_| println!("Saved!"))A button keeps its pointer state under a key made from where it was created. Buttons made in a loop from one line need a key each:
div().flex_row().gap(2.0).children(
["Cut", "Copy", "Paste"].map(|label| button(label).key(format!("edit-{label}"))),
)button("Primary")
.bg_color(Color::rgba(0.3, 0.5, 0.9, 1.0))
.hover_color(Color::rgba(0.4, 0.6, 1.0, 1.0))
.pressed_color(Color::rgba(0.2, 0.4, 0.8, 1.0))
.text_color(Color::WHITE)
.rounded(8.0)
.px(4.0)The colours default to the theme's primary tokens. .id(..) and
.class(..) let a stylesheet style the button, including :hover,
:active and :disabled.
The label takes a string or a signal, and .disabled(..) takes a bool or a
signal. Both are patched in place:
use blinc_core::reactive::{computed, signal};
let saving = signal(false);
let label = computed(move |g| {
if g.get(saving).unwrap_or(false) { "Saving…".to_string() } else { "Save".to_string() }
});
button(label)
.disabled(saving)
.on_click(move |_| saving.set(true))button_with builds the content once and hands it a ButtonLook. Bind
what should follow the button's state to it:
use blinc_layout::widgets::button_with;
button_with(|look| {
div()
.flex_row()
.gap(2.0)
.items_center()
.child(svg(SAVE_ICON).size(16.0, 16.0).color(look.text_color()))
.child(text("Save").color(look.text_color()))
})
.on_click(|_| save_file())look.text_color() is the text colour for the button's current state,
after stylesheet overrides. look.interaction() gives its hovered, pressed
and focused signals.
button("Cannot Click")
.disabled(true)
.disabled_color(Color::rgba(0.2, 0.2, 0.25, 0.5))
.disabled_text_color(Color::rgba(1.0, 1.0, 1.0, 0.4))
.flat_when_disabled(true)A disabled button has its disabled fill and does not click.
.disabled_border(width, color) replaces its border while disabled.
use blinc_core::context_state::use_state_keyed;
use blinc_layout::widgets::checkbox::checkbox;
let remember = use_state_keyed("remember_me", || false);
checkbox(&remember).on_change(|checked| println!("Checkbox is now: {checked}"))The checkbox reads and writes the State<bool> it is given, so the state
is the source of truth: set it from elsewhere and the box follows.
checkbox(&remember)
.label("Remember me")
.label_color(Color::WHITE)checkbox_labeled(&remember, "Remember me") is the same.
checkbox(&remember)
.check_color(Color::WHITE)
.checked_bg(Color::rgba(0.4, 0.6, 1.0, 1.0))
.unchecked_bg(Color::rgba(0.2, 0.2, 0.25, 1.0))
.rounded(4.0)
.checkbox_size(20.0)let remember = use_state_keyed("remember_me", || true); // Start checkeduse blinc_layout::widgets::text_input::{text_input, text_input_state_with_placeholder};
fn my_ui(ctx: &WindowedContext) -> impl ElementBuilder {
let state = text_input_state_with_placeholder("Enter your name...");
text_input(&state)
.w(300.0)
.on_change(|text| {
println!("Input: {}", text);
})
}text_input(&state)
.w(300.0)
.rounded(8.0)
.idle_bg_color(Color::rgba(0.15, 0.15, 0.2, 1.0))
.text_color(Color::WHITE)
.placeholder_color(Color::rgba(0.5, 0.5, 0.6, 1.0))
.focused_border_color(Color::rgba(0.4, 0.6, 1.0, 1.0))let state = text_input_state();
// Later, read the current value
let current_text = state.lock().unwrap().value.clone();The field is built once. Typing, focus, hover and scrolling patch what it
shows in place. After changing its data from outside the widget, as a
stepper button does, call refresh_text_input so the field shows the
change:
use blinc_layout::widgets::text_input::refresh_text_input;
{
let mut data = state.lock().unwrap();
data.value = "42".to_string();
data.cursor = data.value.chars().count();
}
refresh_text_input(&state);input_otp renders linked single-character text input slots for
verification codes and PINs. It keeps a joined State<String> in sync
while handling focus movement across slots.
use blinc_layout::widgets::input_otp::input_otp;
fn verification_code(ctx: &WindowedContext) -> impl ElementBuilder {
let code = ctx.use_state_keyed("otp_code", || String::new());
input_otp(&code, 6)
.numeric_only(true)
.on_complete(|code| println!("complete code: {code}"))
}- Typing a character advances focus to the next slot.
- Backspace on an empty slot rewinds focus and clears the previous slot.
- Pasting distributes characters across the remaining slots.
-
numeric_only(true)filters typed and pasted input to ASCII digits.
input_otp(&code, 6)
.numeric_only(true)
.masked(false)
.gap(8.0)
.class("verification-code")use blinc_layout::widgets::text_area::{text_area, text_area_state_with_placeholder};
fn my_ui(ctx: &WindowedContext) -> impl ElementBuilder {
let state = text_area_state_with_placeholder("Enter description...");
text_area(&state)
.w(400.0)
.rows(6)
.on_change(|text| {
println!("Content: {}", text);
})
}Size and shape are set on the builder. Colours come from the stylesheet, by class or id, per state:
text_area(&state)
.w(400.0)
.rows(6)
.rounded(8.0)
.font_size(14.0)
.class("notes").notes { background: #1e1e2a; color: #ffffff; caret-color: #6699ff; }
.notes:hover { background: #24243a; }
.notes:focus { border-color: #6699ff; }Like the text input, the text area is built once and patches what it shows
in place. After changing its state from outside the widget, call
refresh_text_area so the field shows the change:
use blinc_layout::widgets::text_area::refresh_text_area;
state.lock().unwrap().set_value("First line\nSecond line");
refresh_text_area(&state);use blinc_layout::widgets::code::code;
fn my_ui() -> impl ElementBuilder {
let source = r#"
fn main() {
println!("Hello, Blinc!");
}
"#;
code(source)
.lang("rust")
.w_full()
.h(300.0)
.rounded(8.0)
.font("Fira Code")
.size(14.0)
}-
rust,python,javascript,typescript -
html,css,json,yaml,xml -
sql,bash,go,java,c,cpp - And more...
fn login_form(ctx: &WindowedContext) -> impl ElementBuilder {
let email_state = text_input_state_with_placeholder("Email address");
let password_state = text_input_state_with_placeholder("Password");
let remember_state = use_state_keyed("remember_me", || false);
div()
.w(400.0)
.p(24.0)
.rounded(16.0)
.bg(Color::rgba(0.12, 0.12, 0.16, 1.0))
.flex_col()
.gap(16.0)
// Title
.child(
text("Sign In")
.size(24.0)
.weight(FontWeight::Bold)
.color(Color::WHITE)
)
// Email field
.child(
div()
.flex_col()
.gap(4.0)
.child(label("Email").color(Color::WHITE))
.child(
text_input(&email_state)
.w_full()
.rounded(8.0)
)
)
// Password field
.child(
div()
.flex_col()
.gap(4.0)
.child(label("Password").color(Color::WHITE))
.child(
text_input(&password_state)
.w_full()
.rounded(8.0)
// Note: password masking would be a feature to add
)
)
// Remember me
.child(
checkbox(&remember_state)
.label("Remember me")
.label_color(Color::WHITE)
)
// Submit button
.child(
button("Sign In")
.w_full()
.bg_color(Color::rgba(0.3, 0.5, 0.9, 1.0))
.text_color(Color::WHITE)
.rounded(8.0)
.on_click(|_| {
println!("Form submitted!");
})
)
}| Widget | State | Source |
|---|---|---|
| Button | Hovered, pressed, focused, disabled | Signals from Interaction; .disabled(..)
|
| Checkbox | Checked, hovered | The State<bool> it is given; pointer signals |
| TextInput |
TextFieldState: Idle, Hovered, Focused, FocusedHovered, Disabled |
Its own state handle |
| TextArea | TextFieldState |
Same as TextInput |
-
Use unique keys for state - Each widget needs its own state key.
-
Handle validation in on_change - Validate input as users type.
-
Provide visual feedback - Use colors to indicate focus and errors.
-
Group related inputs - Use flex containers to organize forms.
-
Add labels - Every input should have an associated label for accessibility.
Getting Started
Mobile Development
Example Gallery
- Example Gallery
- Canvas Element
- Canvas Kit Interactive
- Carousel Demo - Selector API Showcase
- Chrome-Style Tabs
- blinc_cn Components
- Code Element
- Complex SVG
- CSS Debug
- CSS Visual Features
- Layer Effects
- Emoji and HTML Entities
- @flow Shader
- Fluid Surface
- Skeleton animation with glTF +
blinc_canvas_kit. DrawContext::run_gpu_passend-to-end demo.- Image CSS Styling
- Image Layer Test
- Keyframe Animation Canvas
- Markdown Editor
- 3D Mesh Demo — renders the Khronos glTF
DamagedHelmetsample model - Motion Demo
- Music Player Glass Card
- Node-editor demo — pre-wired graph with three node types, typed
- Notch Menu Bar
- Overflow Fade
- Overlay System
- Pointer Query
- Rich Text Element
- Rich Text Editor
- Scroll Container
- Semantic @flow
- Sortable
- Stateful API + Signal-bound modifiers demo.
- End-to-end 3D demo wiring Blinc's SceneKit3D renderer up to
- Unified Styling API
- SVG Animation
- Table Builder
- Tabler Icons
- Minimal text positioning test
- Text Input Widgets
- KHR_texture_transform
- Theme System
- Timeline Animation
- Typography
- Video Player
- Wet Glass
- Windowed Application
Web Development
Core Concepts
- Elements & Layout
- Styling & Materials
- CSS Styling
- Theming
- Event Handling
- State Management
- Lists & Conditional Content
- Element Refs
Animation
Components
Component Library (blinc_cn)
Widgets
- Buttons & Inputs
- Text & Rich Text
- Code Editor
- Scroll Containers
- Virtualized List
- Canvas Drawing
- Images & SVG
- Audio & Video
- Markdown Rendering
Canvas Kit
Advanced
- Routing & Navigation
- Multi-Window
- System Integration
- Element Query API
- Overlay System
- Custom State Machines
- Pointer Query
- Performance Tips
- Flow Shaders
- 3D Rendering
- Custom GPU Passes
- Hot-reload (experimental)
Architecture
Contributing