-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix: make focus visible to improve clarity for keyboard users #2222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
c08dcc1
d3bf734
14d9869
6568fa9
611f9f4
f467892
461256b
99eba62
b3b2bbc
a41e53f
aec66d2
308bf54
d2530fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,13 @@ export type CardItem = { | |
| buttonIcon?: 'arrow-right' | 'external-arrow' | ||
| } | ||
|
|
||
| export default function Card({ title, href, description, theme, buttonIcon = "arrow-right" }: CardItem) { | ||
| export default function Card({ | ||
| title, | ||
| href, | ||
| description, | ||
| theme, | ||
| buttonIcon = 'arrow-right', | ||
| }: CardItem) { | ||
| const [isHovered, setIsHovered] = useState(false) | ||
|
|
||
| return ( | ||
|
|
@@ -47,7 +53,7 @@ export default function Card({ title, href, description, theme, buttonIcon = "ar | |
|
|
||
| {href && ( | ||
| <Button | ||
| as="button" | ||
| as="div" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Button Component Fails When Using
|
||
| label={false} | ||
| type={theme === 'dark' ? 'secondary' : 'primary'} | ||
| icon={buttonIcon} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| /* Focus Management System - WCAG 2.4.7 & 1.4.11 Compliant */ | ||
|
|
||
| /* | ||
| * Focus indicators with proper contrast ratios (minimum 3:1) | ||
| * Uses focus-visible for better UX (keyboard navigation only) | ||
| */ | ||
|
|
||
| :root { | ||
| /* Focus indicator colors with high contrast */ | ||
| --focus-outline-color-light: #0066cc; /* Blue with high contrast */ | ||
| --focus-outline-color-dark: #4da6ff; /* Lighter blue for dark mode */ | ||
| --focus-outline-width: 2px; | ||
| --focus-outline-offset: 2px; | ||
| --focus-outline-style: solid; | ||
|
|
||
| /* Alternative focus colors for different contexts */ | ||
| --focus-outline-warning: #ff6b00; /* Orange for warnings/errors */ | ||
| --focus-outline-success: #00a651; /* Green for success states */ | ||
|
|
||
| /* Box shadow focus for cases where outline doesn't work well */ | ||
| --focus-shadow-light: 0 0 0 2px #ffffff, 0 0 0 4px #0066cc; | ||
| --focus-shadow-dark: 0 0 0 2px #0a0a0a, 0 0 0 4px #4da6ff; | ||
| } | ||
|
|
||
| [data-theme='dark'] { | ||
| --focus-outline-color-light: var(--focus-outline-color-dark); | ||
| --focus-shadow-light: var(--focus-shadow-dark); | ||
| } | ||
|
|
||
| /* Remove default focus styles only when we provide alternatives */ | ||
| *:focus { | ||
| outline: auto; | ||
| } | ||
|
|
||
| /* Apply focus-visible for keyboard navigation */ | ||
| *:focus-visible { | ||
| outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color-light); | ||
| outline-offset: var(--focus-outline-offset); | ||
| } | ||
|
|
||
| /* Enhanced focus for interactive elements */ | ||
| a:focus-visible, | ||
| button:focus-visible, | ||
| [role='button']:focus-visible, | ||
| [role='link']:focus-visible, | ||
| [tabindex]:focus-visible { | ||
| outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color-light); | ||
| outline-offset: var(--focus-outline-offset); | ||
| } | ||
|
|
||
| /* Form controls need special handling */ | ||
| input:focus-visible, | ||
| textarea:focus-visible, | ||
| select:focus-visible { | ||
| outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color-light); | ||
| outline-offset: 1px; /* Tighter offset for form controls */ | ||
| } | ||
|
|
||
| /* Checkbox and radio buttons */ | ||
| input[type='checkbox']:focus-visible, | ||
| input[type='radio']:focus-visible { | ||
| outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color-light); | ||
| outline-offset: 2px; | ||
| } | ||
|
|
||
| /* For elements where outline might be cut off, use box-shadow */ | ||
| .focus-shadow:focus-visible { | ||
| outline: none; | ||
| box-shadow: var(--focus-shadow-light); | ||
| } | ||
|
|
||
| /* High contrast mode support */ | ||
| @media (prefers-contrast: high) { | ||
| *:focus-visible { | ||
| outline-width: 3px; | ||
| outline-color: highlight; | ||
| } | ||
| } | ||
|
|
||
| /* Focus for elements that might have complex backgrounds */ | ||
| .focus-high-contrast:focus-visible { | ||
| outline: 3px solid var(--focus-outline-color-light); | ||
| outline-offset: 2px; | ||
| background-color: rgba(255, 255, 255, 0.9); | ||
|
|
||
| [data-theme='dark'] & { | ||
| background-color: rgba(0, 0, 0, 0.9); | ||
| } | ||
| } | ||
|
|
||
| /* Skip link focus (for screen reader users) */ | ||
| .skip-link:focus-visible { | ||
| position: absolute; | ||
| top: 10px; | ||
| left: 10px; | ||
| z-index: 9999; | ||
| padding: 8px 16px; | ||
| background: var(--focus-outline-color-light); | ||
| color: white; | ||
| text-decoration: none; | ||
| border-radius: 4px; | ||
| outline: 2px solid white; | ||
| outline-offset: 2px; | ||
| } | ||
|
|
||
| /* Ensure focus is visible even when using custom components */ | ||
| .custom-focus:focus-visible { | ||
| position: relative; | ||
| } | ||
|
|
||
| .custom-focus:focus-visible::after { | ||
| content: ''; | ||
| position: absolute; | ||
| top: -2px; | ||
| right: -2px; | ||
| bottom: -2px; | ||
| left: -2px; | ||
| border: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color-light); | ||
| border-radius: inherit; | ||
| pointer-events: none; | ||
| } | ||
|
|
||
| /* Focus indicators that work well with rounded corners */ | ||
| .focus-rounded:focus-visible { | ||
| outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color-light); | ||
| outline-offset: var(--focus-outline-offset); | ||
| border-radius: inherit; | ||
| } | ||
|
|
||
| /* Error state focus */ | ||
| .focus-error:focus-visible { | ||
| outline-color: var(--focus-outline-warning); | ||
| } | ||
|
|
||
| /* Success state focus */ | ||
| .focus-success:focus-visible { | ||
| outline-color: var(--focus-outline-success); | ||
| } | ||
|
|
||
| /* Reduced motion support */ | ||
| @media (prefers-reduced-motion: no-preference) { | ||
| *:focus-visible { | ||
| transition: outline-color 0.15s ease-in-out; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Button Component Fails with
as="div"The
Buttoncomponent, whenas="div", fails to resolve the component type iflinkComponentMaplacks a 'div' entry, causing a runtime error ('Element type is invalid'). Additionally, its logic incorrectly passes anchor-specific attributes (href,target,rel,download,disabled) to thedivelement (due to applying them wheneveras !== 'button'), resulting in invalid HTML and React warnings. TheButtoncomponent should ensure proper component resolution foras="div"and only apply anchor-related attributes for 'a' or 'link' types.Additional Locations (1)
src/components/elements/buttons/button/index.tsx#L23-L24