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.