diff --git a/src/components/NewTaskPlaceholder.tsx b/src/components/NewTaskPlaceholder.tsx
index 33a2db41..e64c2f0c 100644
--- a/src/components/NewTaskPlaceholder.tsx
+++ b/src/components/NewTaskPlaceholder.tsx
@@ -1,5 +1,5 @@
import { onMount, onCleanup } from 'solid-js';
-import { toggleNewTaskDialog, createTerminal, store, unfocusPlaceholder } from '../store/store';
+import { toggleNewTaskDialog, createTerminal, unfocusPlaceholder } from '../store/store';
import { registerFocusFn, unregisterFocusFn } from '../store/focus';
import { theme } from '../lib/theme';
import { mod } from '../lib/platform';
@@ -17,18 +17,6 @@ export function NewTaskPlaceholder() {
});
});
- const isFocused = (btn: 'add-task' | 'add-terminal') =>
- store.placeholderFocused && store.placeholderFocusedButton === btn;
-
- const focusedBorder = (btn: 'add-task' | 'add-terminal') =>
- isFocused(btn) ? `2px dashed ${theme.accent}` : `2px dashed ${theme.border}`;
-
- const focusedColor = (btn: 'add-task' | 'add-terminal') =>
- isFocused(btn) ? theme.accent : theme.fgSubtle;
-
- const focusedBg = (btn: 'add-task' | 'add-terminal') =>
- isFocused(btn) ? `color-mix(in srgb, ${theme.accent} 8%, transparent)` : undefined;
-
return (
{
+ it('uses a low-specificity app-wide default that components can override', () => {
+ const rule = css.match(/(?:^|\n):where\(([\s\S]*?)\):focus-visible\s*\{([^}]*)\}/);
+
+ expect(rule).not.toBeNull();
+ expect(rule?.[1].split(',').map((selector) => selector.trim())).toEqual(globalFocusSelectors);
+ expect(rule?.[2]).toMatch(/outline:\s*2px solid var\(--accent\)/);
+ expect(css).not.toMatch(/(?:^|\n):is\([\s\S]*?\):focus-visible/);
+ });
+
+ it('uses the DOM focus indicator as the placeholder focus state', () => {
+ expect(css).not.toMatch(/\.new-task-placeholder:focus-visible\s*\{[^}]*outline:\s*none/);
+ expect(placeholder).not.toContain('store.placeholderFocused');
+ expect(placeholder).not.toMatch(/focused(?:Border|Color|Bg)/);
+ });
+});
diff --git a/src/styles.css b/src/styles.css
index 227949a2..8cb12594 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -69,31 +69,28 @@ body.font-smoothing {
text-rendering: geometricPrecision;
}
-/* Visible keyboard focus indicators inside dialogs.
- Scoped to .dialog-panel (a class hook) so it works through Solid
- mounting, where descendant selectors rooted at the app shell would not
- match. The selector list mirrors the spec's interactive-role enumeration
- so icon buttons, toggles, and ARIA widgets all pick up the indicator. */
-.dialog-panel
- :is(
- button,
- input,
- select,
- textarea,
- a[href],
- [tabindex]:not([tabindex='-1']),
- [role='button'],
- [role='switch'],
- [role='checkbox'],
- [role='link'],
- [role='menuitem'],
- [role='menuitemradio'],
- [role='menuitemcheckbox'],
- [role='tab'],
- [role='option'],
- [role='combobox'],
- [role='radio']
- ):focus-visible {
+/* Visible keyboard focus indicators for interactive controls. The selector
+ includes native elements and ARIA widgets so keyboard focus remains visible
+ across the app, including content mounted through Solid . */
+:where(
+ button,
+ input,
+ select,
+ textarea,
+ a[href],
+ [tabindex]:not([tabindex='-1']),
+ [role='button'],
+ [role='switch'],
+ [role='checkbox'],
+ [role='link'],
+ [role='menuitem'],
+ [role='menuitemradio'],
+ [role='menuitemcheckbox'],
+ [role='tab'],
+ [role='option'],
+ [role='combobox'],
+ [role='radio']
+):focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
@@ -1510,10 +1507,6 @@ textarea::placeholder {
background 0.15s ease;
}
-.new-task-placeholder:focus-visible {
- outline: none;
-}
-
.new-task-placeholder:hover {
border-color: var(--accent) !important;
color: var(--accent) !important;