After 3 days of attempting to implement smart default actions, the core issue was:
- Converting stored
'default'action to'more-info'for display caused HA's form system to detect a mismatch - This mismatch triggered constant re-rendering, causing the dropdown to revert to previous values
- CSS and DOM manipulation attempts to hide blank spaces were band-aids that didn't address the root cause
The fundamental problem: Home Assistant's ui_action selector expects the displayed value to match the stored value. Any conversion between the two creates a sync issue that causes dropdown instability.
Instead of trying to display 'default' as something else:
- Don't store
'default'at all when user hasn't made an explicit choice - Use
undefinedin the config to represent "smart resolution desired" - Display
'more-info'in the dropdown as the natural first choice - Resolve at runtime based on entity domain when action is
undefinedor'default'
Display Logic:
// If action is undefined/null, show 'more-info' as default
// This represents smart behavior without storing anything
const displayAction =
action && action.action
? action.action === 'nothing'
? { ...action, action: 'none' }
: action
: { action: 'more-info', entity: moduleEntity };
// Track whether we have an explicit stored action
const hasExplicitAction = action && action.action;Event Handler:
if (newAction.action === 'more-info' && !hasExplicitAction) {
// User clicked "More Info" but no action is stored (smart default)
// Keep it undefined so smart resolution continues
updateAction(undefined);
} else if (newAction.action === 'none') {
updateAction({ action: 'nothing' });
} else {
// User selected specific action - store it explicitly
updateAction(cleanAction);
}static handleAction(
action: TapActionConfig | undefined,
hass: HomeAssistant,
element?: HTMLElement,
config?: UltraCardConfig
): void {
// If action is undefined or missing, or explicitly 'default', use smart resolution
let resolvedAction: TapActionConfig;
if (!action || !action.action || action.action === 'default') {
resolvedAction = this.resolveDefaultAction(action || { action: 'default' }, hass);
} else {
resolvedAction = action;
}
// ... rest of action handling
}The resolveDefaultAction() method checks entity domain:
button.*→perform-action: button.pressautomation.*→togglescript.*→perform-action: script.turn_onswitch.*,light.*, etc. →toggle- All others →
more-info
When no explicit action is stored:
- Dropdown shows: "More Info" (first item, natural default)
- Header shows: "Smart Default Configuration"
- Description shows: "Entity will use its native action (button press, automation toggle, script run, etc.)"
- ✅ What we display (
'more-info') matches what HA's form expects - ✅ No conversion between stored and displayed values
- ✅ HA's form system stays in sync
- ✅
undefinedin config = "use smart resolution" - ✅ Only explicit user choices are stored
- ✅ Clean configuration files
- ✅ Existing
{ action: 'default' }configs still work - ✅ Explicit actions (toggle, navigate, etc.) unchanged
- ✅ No migration needed
- ✅ No blank spaces
- ✅ No revert-to-previous-value bugs
- ✅ Stable UI that works like standard HA components
- User adds module with
button.garage_door - Actions tab shows "More Info" selected (but nothing stored)
- User taps card → triggers
button.pressservice - Smart resolution in action! 🎉
- User opens Actions tab
- Sees "More Info" with "Smart Default Configuration" header
- Changes to "Toggle" explicitly
- Now stores
{ action: 'toggle', entity: 'button.garage_door' } - Tap now toggles (overriding smart behavior)
- Module originally had
button.test - User changes entity to
automation.lights - Action still undefined (smart default)
- Tap now toggles the automation (no config change needed)
✅ Dropdown Stability: No revert-to-previous-value
✅ No Blank Spaces: Clean dropdown rendering
✅ Smart Resolution: Button → press, Automation → toggle
✅ Entity Swapping: Changing entity maintains smart behavior
✅ Explicit Override: User can still select specific actions
✅ Backwards Compatible: Existing configs work unchanged
-
src/components/ultra-link.ts- Updated
handleAction()to acceptundefinedactions - Handles both
undefinedand'default'for backwards compatibility
- Updated
-
src/tabs/global-actions-tab.ts- Removed all value conversion logic
- Display
undefinedas'more-info'naturally - Only store explicit user selections
- Smart UI feedback when no action stored
Stop trying to make 'default' appear in the UI. Let undefined be undefined, display it as 'more-info' naturally, and resolve at runtime. This aligns with how HA's form system expects to work and eliminates all the dropdown issues.
The 3-day struggle was trying to force a custom action type into HA's selector. The solution was to work with the UI system, not against it.