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.
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.
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.
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.
- Zero-styling: behavior only - you own presentation
- Accessible: correct semantics and keyboard navigation
- Observable state: hover, focus, press, drag, select
- Composable: small, predictable APIs
- 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
- NakedDisclosure - standalone show/hide button and panel
- NakedMenu - anchored overlay menu
- NakedDialog - modal dialog behavior + focus trap
- NakedLink - link semantics, activation, and observable interaction state
- NakedTooltip - anchored tooltip with lifecycle
- NakedPopover - anchored, dismissible popover overlay
- NakedSwitch - on/off control with switch semantics
- NakedTextField - native text editing with custom styling
dependencies:
naked_ui: ^1.0.0-beta.4 # https://pub.dev/packages/naked_uiThen: flutter pub get
Import: import 'package:naked_ui/naked_ui.dart';
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.
NakedButtonmanages interaction behavior and accessibilitybuilderfunction defines visual appearance based on statestate.isPressedprovides current press state- Return any Widget for custom styling
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.
Common patterns for enhanced interactivity:
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.
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(),
)All components follow the same builder pattern. Choose based on interaction requirements:
- Button - Any clickable action (submit, save, delete, etc.)
- Toggle - On/off switches (dark mode, notifications)
- Checkbox - Select multiple options from a list
- Radio - Pick one option from a group
- TextField - Text input (names, messages, search)
- Select - Choose from a dropdown list (countries, categories)
- Slider - Pick numeric values (volume, price range)
- Tabs - Switch between different content sections
- Accordion - Expandable sections (FAQ, settings groups)
- Disclosure - Independently show or hide one content section
- Dialog - Modal windows (confirmations, forms)
- Tooltip - Show help text on hover
- Popover - Show additional info or controls
- Menu - Context menus and dropdown actions
Recommended approach: Begin with Button - the simplest component for learning the core patterns. Other components use the same builder-based approach.
Next steps: Review component-specific documentation for detailed implementation examples and API references.