Getting Started
Behavior-first, zero-styling Flutter components
The Naked UI library provides headless components that handle functionality, interaction, and accessibility without imposing any visual styling. This approach gives you full control over your design system while ensuring components work correctly and meet accessibility standards.
What is headless UI?
Headless components handle interaction behavior and accessibility without visual styling. They provide:
- Keyboard navigation (pressing Tab, Enter, arrow keys)
- Screen reader announcements
- Mouse interactions (hover, click, drag)
- Focus management
- Touch gestures
Headless components provide behavior and accessibility without visual styling.
Standard UI library approach
Most UI libraries give you components that look like this:
// Standard UI library approach
ElevatedButton(
onPressed: () {},
child: const Text('Click me'), // Fixed appearance
)
Standard libraries limit customization to predefined themes and styles. Design flexibility is constrained.
The Naked UI approach
Naked UI provides custom styling through builders:
NakedButton(
onPressed: () {},
builder: (context, state, child) {
// Custom styling based on state
return Container(
decoration: BoxDecoration(
color: state.isPressed ? Colors.blue.shade700 : Colors.blue,
borderRadius: BorderRadius.circular(8),
// Customize any aspect of design
),
child: const Text('Click me'),
);
},
)
Components handle behavior. Builders define appearance.
Key Features
- Zero-styling: behavior only - you own presentation
- Accessible: correct semantics and keyboard navigation
- Observable state: hover, focus, press, drag, select
- Composable: small, predictable APIs
Components
- 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
Installation
dependencies:
naked_ui: ^latest_version # https://pub.dev/packages/naked_ui
Then: flutter pub get
Import: import 'package:naked_ui/naked_ui.dart';
Basic implementation
Build a simple button:
import 'package:flutter/material.dart';
import 'package:naked_ui/naked_ui.dart';
NakedButton(
onPressed: () => print('Hello World!'),
builder: (context, state, child) {
return Container(
padding: const EdgeInsets.all(12),
color: state.isPressed ? Colors.blue : Colors.grey,
child: const Text('Submit'),
);
},
)
This button includes:
- Press state color changes
- Automatic keyboard navigation
- Screen reader support
- Mouse and touch interaction
The color changes when pressed due to the state.isPressed
condition.
Implementation details
NakedButton
manages interaction behavior and accessibilitybuilder
function defines visual appearance based on statestate.isPressed
provides current press state- Return any Widget for custom styling
Enhanced patterns
Multiple states: Use state.when()
to handle pressed, hovered, and focused states
Animations: Wrap containers in AnimatedContainer for smooth transitions
Advanced styling: Add borders, shadows, gradients, or any visual elements
Check out our Button documentation for advanced examples and patterns.
Multiple interaction states
Handle pressed, hovered, and focused states:
import 'package:flutter/material.dart';
import 'package:naked_ui/naked_ui.dart';
NakedButton(
onPressed: () {},
builder: (context, state, child) {
// Method 1: Check each state individually
Color buttonColor = Colors.grey;
if (state.isPressed) buttonColor = Colors.blue.shade800;
else if (state.isHovered) buttonColor = Colors.blue.shade600;
else if (state.isFocused) buttonColor = Colors.blue.shade400;
// Method 2: Use state.when() for cleaner code
final color = state.when(
pressed: Colors.blue.shade800, // Darkest when clicked
hovered: Colors.blue.shade600, // Medium when mouse over
focused: Colors.blue.shade400, // Light when keyboard focused
orElse: Colors.grey, // Default state
);
return Container(
padding: const EdgeInsets.all(12),
color: color,
child: const Text('Interactive button'),
);
},
)
The state.when()
method provides cleaner conditional logic.
Programmatic focus control
For precise focus management in forms or complex UIs:
import 'package:flutter/material.dart';
import 'package:naked_ui/naked_ui.dart';
final myFocusNode = FocusNode();
NakedButton(
focusNode: myFocusNode, // Use your own focus node
autofocus: true, // Focus automatically when built
onPressed: () {
myFocusNode.requestFocus(); // Focus programmatically
},
builder: (context, state, child) => YourCustomWidget(),
)
Component selection guide
All components follow the same builder pattern. Choose based on interaction requirements: