Customization
Every Duxt UI component supports className overrides, HTML attributes, and event handlers for full control.
Class Overrides with className
All components accept a className
prop. When you pass Tailwind classes, they intelligently replace conflicting base classes using twMerge. For example, passing
className: 'bg-purple-500'
to a primary button replaces bg-cyan-500
instead of appending both.
// Default button
DButton(label: 'Default', color: DButtonColor.primary)
// Override background color - replaces bg-cyan-500
DButton(label: 'Purple BG', className: 'bg-purple-500 hover:bg-purple-600')
// Override border radius
DButton(label: 'Rounded Full', className: 'rounded-full')
// Add extra classes (non-conflicting classes are appended)
DButton(label: 'Large Shadow', className: 'shadow-xl')
Smart Class Merging
The twMerge
utility resolves conflicts between these Tailwind utility groups:
| Group | Prefix | Example |
|---|---|---|
| Background | bg-* | bg-red-500 replaces bg-cyan-500 |
| Text Color | text-{color} | text-white replaces text-gray-700 |
| Text Size | text-{size} | text-lg replaces text-sm |
| Font Weight | font-* | font-bold replaces font-medium |
| Border Radius | rounded* | rounded-full replaces rounded-md |
| Border Color | border-{color} | border-red-500 replaces border-gray-300 |
| Ring | ring* | ring-2 replaces ring |
| Shadow | shadow* | shadow-xl replaces shadow-md |
| Padding | p-*, px-*, py-* | p-8 replaces p-4 |
| Margin | m-*, mx-*, my-* | mt-4 replaces mt-2 |
| Width/Height | w-*, h-* | w-full replaces w-auto |
| Display | flex, block, etc. | block replaces inline-flex |
Classes that do not conflict are always kept. Variant prefixes like dark:,
hover:,
focus:
are handled correctly and only conflict within the same variant.
Works on All Components
Custom
Red border alert
Card with cyan border
// Alert with custom border
DAlert(title: 'Custom', description: 'Red border', className: 'border-red-500')
// Badge with custom colors
DBadge(label: 'Custom', className: 'bg-purple-500 text-white')
// Card with custom border
DCard(className: 'border-cyan-500', children: [...])
HTML id
Pass id
to set the HTML id attribute on the root element. Useful for anchors, JavaScript integration, and testing.
DButton(label: 'Submit', id: 'submit-btn')
DInput(placeholder: 'Email', id: 'email-input')
DModal(id: 'settings-modal', trigger: DButton(label: 'Settings'), children: [...])
HTML Attributes
Pass any HTML attribute via the attributes
map. These are applied to the root element. Components that have internal attributes (like ARIA labels) merge them so your values take precedence.
// Data attributes for testing
DButton(
label: 'Delete',
attributes: {'data-testid': 'delete-btn', 'data-action': 'delete'},
)
// ARIA attributes
DInput(
placeholder: 'Search...',
attributes: {'aria-label': 'Search products', 'role': 'searchbox'},
)
// Custom data attributes
DCard(
attributes: {'data-card-id': '42', 'data-category': 'featured'},
children: [...],
)
Event Handlers
Add custom event handlers via the events
map. Any DOM event is supported. These are applied to the root element alongside any built-in handlers.
// Mouse events
DButton(
label: 'Hover me',
events: {
'mouseenter': (e) => showTooltip(),
'mouseleave': (e) => hideTooltip(),
},
)
// Keyboard events
DInput(
placeholder: 'Press Enter...',
events: {
'keydown': (e) => handleKeydown(e),
},
)
// Focus events
DCard(
events: {
'focusin': (e) => highlight(),
'focusout': (e) => unhighlight(),
},
children: [...],
)
Using twMerge Directly
The twMerge
and mergeAttributes
utilities are exported from duxt_ui and can be used in your own components:
import 'package:duxt_ui/duxt_ui.dart';
// Smart class merging - conflicting classes are replaced
twMerge('bg-cyan-500 text-white rounded-md p-4', 'bg-red-500 rounded-full')
// => 'text-white p-4 bg-red-500 rounded-full'
// Non-conflicting classes are appended
twMerge('flex items-center gap-2', 'shadow-lg border')
// => 'flex items-center gap-2 shadow-lg border'
// Merge HTML attribute maps
mergeAttributes({'data-modal': 'true'}, {'data-testid': 'my-modal'})
// => {'data-modal': 'true', 'data-testid': 'my-modal'}