A Flutter UI library for headless widgets. No styling, just behavior. Build custom UIs with full semantics and observable states like hovered, focused, pressed, dragged, and others.
- No styling: Completely naked components for total design control.
- Full semantics: Built-in accessibility for screen readers and assistive tools.
- Observable states: Track hover, focus, drag, and more.
- Builder APIs: Composable widgets for custom UI logic.
The complete documentation covers detailed component APIs and examples, guides and best practices, accessibility implementation details, as well as advanced usage patterns and customization.
- NakedButton — button interactions (hover, press, focus)
- NakedCheckbox — toggle behavior and semantics
- NakedRadio — single‑select radio with group management
- NakedSelect — dropdown/select with keyboard navigation
- NakedSlider — value slider with drag + keys
- NakedToggle — toggle button or switch behavior
- NakedTabs — tablist + roving focus
- NakedAccordion — expandable/collapsible sections
- NakedMenu — anchored overlay menu
- NakedDialog — modal dialog behavior + focus trap
- NakedTooltip — anchored tooltip with lifecycle
- NakedPopover — anchored, dismissible popover overlay
-
Build your custom visuals using standard Flutter widgets
-
Wrap the visuals in the corresponding Naked component
-
React to typed state callbacks or use the builder snapshot to style interaction states
-
Create your visual design: Design your UI components using standard Flutter widgets
-
Wrap with Naked behavior: Wrap your design with the appropriate Naked component
-
Handle state changes: Use the builder pattern to access component state and update your visual design accordingly
Below are examples of using NakedButton
, NakedCheckbox
, and NakedMenu
. Each shows how to wrap custom visuals with headless behavior and handle states using the builder pattern. See the full documentation for all components.
Create a button with custom styling that responds to interaction states.
NakedButton(
onPressed: () => print('Clicked'),
builder: (context, state, child) => Container(
padding: const EdgeInsets.all(12),
color: state.when(
pressed: Colors.blue.shade900,
hovered: Colors.blue.shade700,
focused: Colors.blue.shade600,
orElse: Colors.blue,
),
child: const Text('Click Me', style: TextStyle(color: Colors.white)),
),
)
Build a checkbox with custom visuals while maintaining proper state management.
class SimpleCheckbox extends StatefulWidget {
const SimpleCheckbox({super.key});
@override
State<SimpleCheckbox> createState() => _SimpleCheckboxState();
}
class _SimpleCheckboxState extends State<SimpleCheckbox> {
bool checked = false;
@override
Widget build(BuildContext context) {
return NakedCheckbox(
value: checked,
onChanged: (value) => setState(() => checked = value!),
builder: (context, state, child) => Container(
width: 24,
height: 24,
color: state.when(
hovered: Colors.grey.shade300,
focused: Colors.blue.shade100,
orElse: state.isChecked ? Colors.blue : Colors.grey.shade200,
),
child: state.isChecked ? const Icon(Icons.check, size: 16) : null,
),
);
}
}
Create a dropdown menu with custom styling and menu items.
NakedMenu<String>(
onSelected: (value) => print('Selected: $value'),
builder: (context, state, child) => Container(
padding: const EdgeInsets.all(8),
color: state.when(
hovered: Colors.grey.shade300,
pressed: Colors.grey.shade400,
orElse: state.isOpen ? Colors.grey.shade200 : Colors.white,
),
child: Text(state.isOpen ? 'Close' : 'Menu'),
),
overlayBuilder: (context, info) => Container(
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
NakedMenuItem(
value: 'edit',
builder: (context, state, child) => Container(
padding: const EdgeInsets.all(8),
color: state.isHovered ? Colors.blue.shade100 : Colors.white,
child: const Text('Edit'),
),
),
NakedMenuItem(
value: 'delete',
builder: (context, state, child) => Container(
padding: const EdgeInsets.all(8),
color: state.isHovered ? Colors.red.shade100 : Colors.white,
child: const Text('Delete'),
),
),
],
),
),
)
Naked UI components use the builder pattern to give you access to the current interaction state, allowing you to drive your own visual design and behavior:
NakedButton(
builder: (context, state, child) {
// Access state properties directly
if (state.isPressed) {
// Handle pressed state
}
if (state.isHovered) {
// Handle hover state
}
if (state.isFocused) {
// Handle focus state
}
// Use state.when() for conditional styling
final color = state.when(
pressed: Colors.blue.shade800,
hovered: Colors.blue.shade600,
orElse: Colors.blue,
);
return YourWidget(color: color);
},
// Other properties...
)
See each component's documentation for details on all available configuration options.