NakedCheckbox
Headless checkbox primitive with tristate support and typed state snapshots
Headless checkbox component. Handles toggle behavior, keyboard activation, and accessibility. Use builder pattern for custom styling.
When to use this
- Form selections: Multiple choice options in forms
- Settings toggles: Enable/disable features or preferences
- List selections: Select multiple items from a list
- Agreement confirmations: Terms of service, privacy policies
See complete examples in our GitHub repository.
Basic implementation
import 'package:flutter/material.dart';
import 'package:naked_ui/naked_ui.dart';
class CheckboxExample extends StatefulWidget {
const CheckboxExample({super.key});
@override
State<CheckboxExample> createState() => _CheckboxExampleState();
}
class _CheckboxExampleState extends State<CheckboxExample> {
bool _isChecked = false;
@override
Widget build(BuildContext context) {
return NakedCheckbox(
value: _isChecked,
onChanged: (next) => setState(() => _isChecked = next ?? false),
builder: (context, state, _) {
final bool isChecked = state.isChecked == true;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: isChecked ? Colors.blue : Colors.transparent,
border: Border.all(color: Colors.blue, width: 2),
borderRadius: BorderRadius.circular(4),
),
child: isChecked
? const Icon(Icons.check, size: 14, color: Colors.white)
: null,
),
const SizedBox(width: 8),
const Text('Label'),
],
);
},
);
}
}
For tristate checkboxes (supporting null/indeterminate state), set tristate: true
and use bool?
for the value. The checkbox will cycle through false → true → null → false when tapped.
Typed Checkbox State
NakedCheckboxState
delivers everything the builder needs:
isChecked
→bool?
(null when tristate and currently indeterminate)tristate
→ whether tristate mode is enabledisIntermediate
→ convenience flag for the null casewidgetStates
→ hover/focus/pressed/disabled information
Access from Context
NakedCheckboxState.of(context)
→ read the nearest scope (throws if missing)NakedCheckboxState.maybeOf(context)
→ nullable variantNakedCheckboxState.controllerOf(context)
→ access the underlyingWidgetStatesController
NakedCheckboxState.maybeControllerOf(context)
→ nullable controller lookup
Constructor
const NakedCheckbox({
Key? key,
this.child,
this.value = false,
this.tristate = false,
this.onChanged,
this.enabled = true,
this.mouseCursor,
this.enableFeedback = true,
this.focusNode,
this.autofocus = false,
this.onFocusChange,
this.onHoverChange,
this.onPressChange,
this.builder,
this.semanticLabel,
})
Key Parameters
value
→ current checked value (bool?
whentristate
)tristate
→ enable false → true → null cyclingonChanged
→ receive the next value; omit to render a disabled checkboxbuilder
→ValueWidgetBuilder<NakedCheckboxState>?
for dynamic visualschild
→ static child when you do not need the builder- interaction callbacks:
onFocusChange
,onHoverChange
,onPressChange
semanticLabel
→ accessible name announced by screen readers
Accessibility Notes
- Ensure the control is labelled either via surrounding text or
semanticLabel
- Provide pressed/hover/focus affordances for keyboard and pointer users
- When using
tristate
, explain the meaning of the intermediate state in adjacent text - Minimum recommended touch target is 44×44 logical pixels