Skip to content
Open
Next Next commit
fix: fix focus
  • Loading branch information
michalconsensys committed Aug 11, 2025
commit c08dcc17835728b4195a0ad3258c9c26a2808785
7 changes: 6 additions & 1 deletion src/components/AuthLogin/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@
top: 50%;
transform: translate(-50%, -50%);
overflow: auto;
outline: none;
background: var(--modal-bg-color-primary);

/* Enhanced focus styles for modal accessibility */
&:focus-visible {
outline: 2px solid var(--focus-outline-color-light);
outline-offset: 2px;
}
width: 60%;
padding: 1.4rem;
border-radius: 1rem;
Expand Down
7 changes: 7 additions & 0 deletions src/components/Card.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@
opacity: 1;
}
}

border: 2px solid transparent;

&:focus {
border: 2px solid var(--focus-outline-color-light);
outline: none;
}
}

.shape {
Expand Down
10 changes: 8 additions & 2 deletions src/components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -47,7 +53,7 @@ export default function Card({ title, href, description, theme, buttonIcon = "ar

{href && (
<Button
as="button"
as="div"
Copy link

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 Button component, when as="div", fails to resolve the component type if linkComponentMap lacks 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 the div element (due to applying them whenever as !== 'button'), resulting in invalid HTML and React warnings. The Button component should ensure proper component resolution for as="div" and only apply anchor-related attributes for 'a' or 'link' types.

Additional Locations (1)
Fix in Cursor Fix in Web

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Button Component Fails When Using as="div"

The Button component in Card.tsx now uses as="div". This is problematic because the Button component relies on linkComponentMap to resolve its underlying element. If linkComponentMap does not include a 'div' entry, the resolved component will be undefined, leading to a React "Element type is invalid" runtime error. Additionally, even if 'div' were supported, the Button component currently passes link-specific props (like href) to non-button elements when as is not 'button', which would result in invalid attributes on the div. This change introduces a high risk of a runtime crash.

Fix in Cursor Fix in Web

label={false}
type={theme === 'dark' ? 'secondary' : 'primary'}
icon={buttonIcon}
Expand Down
8 changes: 7 additions & 1 deletion src/components/Input/input.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@
overflow: hidden;
color: var(--input-color);
font-style: normal;
outline: none;
width: 100%;
margin-bottom: 4px;

/* Enhanced focus styles for better accessibility */
&:focus-visible {
outline: 2px solid var(--focus-outline-color-light);
outline-offset: 1px;
border-color: var(--focus-outline-color-light);
}

&:disabled {
opacity: 0.5;
pointer-events: none;
Expand Down
14 changes: 11 additions & 3 deletions src/components/ParserOpenRPC/InteractiveBox/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,11 @@
background-color: var(--general-gray-dark);
}
}
.formControl:focus {
outline: none;
/* Enhanced focus styles for better accessibility */
.formControl:focus-visible {
outline: 2px solid var(--focus-outline-color-light);
outline-offset: 1px;
border-color: var(--focus-outline-color-light);
}
.formControl[type='number'] {
border-color: transparent;
Expand Down Expand Up @@ -382,11 +385,16 @@
background: none;
padding: 0;
border: 0;
outline: none;
color: var(--general-gray-mid);
font-size: inherit;
line-height: 1;
cursor: pointer;

/* Enhanced focus styles for interactive buttons */
&:focus-visible {
outline: 2px solid var(--focus-outline-color-light);
outline-offset: 2px;
}
}
.addItemIcon {
display: inline-flex;
Expand Down
7 changes: 7 additions & 0 deletions src/components/elements/buttons/button/button.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@

mask-image: radial-gradient(white, black);

border: 2px solid transparent;

&:focus {
border: 2px solid var(--focus-outline-color-light);
outline: none;
}

&:before {
content: '';

Expand Down
2 changes: 1 addition & 1 deletion src/components/elements/buttons/button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface ButtonProps {
href?: string
onClick?: () => void
disabled?: boolean
as?: 'link' | 'a' | 'button'
as?: 'link' | 'a' | 'button' | 'div'
external?: boolean
rel?: string | boolean
download?: boolean | string
Expand Down
1 change: 1 addition & 0 deletions src/scss/app.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import 'commons/reset';
@import 'commons/focus';
@import 'commons/typescale';
@import 'commons/utils';
@import 'commons/animations';
Expand Down
145 changes: 145 additions & 0 deletions src/scss/commons/_focus.scss
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;
}
}
7 changes: 0 additions & 7 deletions src/scss/commons/_reset.scss
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,6 @@ button {
background: none;
}

a,
li,
button {
outline-color: var(--general-white);
}

input,
select,
button,
Expand All @@ -168,7 +162,6 @@ textarea {

appearance: none;
border: none;
outline: none;
}

input[type='submit'] {
Expand Down
9 changes: 8 additions & 1 deletion src/scss/elements/_form.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ select {
border-color calc(var(--motion) * var(--motion-speed) * 0.5) $gentle-ease;

&:focus {
border-color: var(--general-dark);
border-color: var(--focus-outline-color-light);
}

/* Enhanced focus visibility for form controls */
&:focus-visible {
border-color: var(--focus-outline-color-light);
outline: 2px solid var(--focus-outline-color-light);
outline-offset: 1px;
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/scss/theme/_interactive-box.scss
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@
transition: border var(--ifm-transition-fast) ease;

background-color: transparent;

/* Enhanced focus styles for interactive box inputs */
&:focus-visible {
outline: 2px solid var(--focus-outline-color-light);
outline-offset: 1px;
border-color: var(--focus-outline-color-light);
}
}

&#method select,
Expand All @@ -87,6 +94,12 @@
appearance: auto;
width: 1.6rem;
height: 1.6rem;

/* Enhanced focus styles for checkboxes */
&:focus-visible {
outline: 2px solid var(--focus-outline-color-light);
outline-offset: 2px;
}
}

&#interactive-box .form-group {
Expand Down