Making a custom input counter component accessible
This blog post is not a tutorial on how to build an input counter component, but rather a discussion on how to structure the component for accessibility and good practice. The problem of overlapping elements We recently encountered an input counter component that failed the accessibility test performed by Evinced Web Flow Analyzer. The reported error was "There is an overlapping between interactive elements" involving the increment button and the input field. This issue surprised us because, at first glance, the component appeared accessible, with keyboard navigation, voiceover, and focus state working as expected during manual testing. To investigate, we examined the component's implementation. Initially, its HTML template seemed well-structured, with the increment button, input field, and decrement button nested as sibling elements within a div container: + - However, the component's CSS styling revealed a different story. The increment and decrement buttons were absolutely positioned, while the input field was styled with width: calc(100% - 60px) and horizontal padding (padding-inline: 30px), aligning with the button widths: .container { /**... */ width: 150px; position: relative; } .btn { position:absolute; height: 100%; min-width: 30px; } .incr { left: 1px; } .decr { right: 0; } .counter { border:none; padding: 5px; padding-inline: 30px; width: calc(100% - 60px); text-align: center; } While visually effective, this CSS introduced poor positioning practices: Using position: absolute removes buttons from the normal document flow, forcing the browser into additional layout computations. Button positions aren't relative to the input field, causing additional complexity in CSS rules when adjusting font sizes, widths, or container dimensions. Absolute positioning complicates RTL/LTR language support (such as Arabic or Hebrew), requiring extra CSS adjustments. There is no actual necessity for absolute positioning since elements are already correctly structured in HTML. Additionally, the left: 1px rule caused a 1px overlap of the increment button on the input field, directly triggering the accessibility error. Fixing overlapping and RTL issues using CSS Flexbox A simple fix would be adjusting left: 1px to left: 0, removing the immediate overlap. However, this would only patch the bug without addressing the root issue (position: absolute). A better solution is using CSS Flexbox for the container, eliminating absolute positioning entirely. The updated CSS rules remove unnecessary properties (left, right, height: 100%, padding, and calc()): .container { border: 1px solid lightgray; padding:0; display: flex; } .btn { min-width: 30px; } input { border:none; padding: 5px; text-align: center; } However, applying Flexbox introduces an overflow issue for the input field, as form elements inherently have a default width set by browser stylesheets. Resolving input overflow in Flexbox To address the overflow, set min-width: 0 and flex: 1 on the input field. This ensures it dynamically shrinks or grows within the available container space: input { /** ... */ min-width: 0; flex: 1; } Alternatively, using a fieldset instead of a div container could omit the min-width: 0 property. This provides a semantically meaningful solution beneficial for accessibility. Supporting RTL/LTR languages With Flexbox, the component inherently supports RTL/LTR languages without additional CSS rules. To verify this, use the dir attribute on the container: + - This approach automatically swaps the increment and decrement buttons appropriately, maintaining a centered input field. Summary In this blog post, we addressed accessibility issues caused by overlapping elements in an input counter component due to absolute positioning. By replacing this with CSS Flexbox and adjusting the CSS style of input field, we significantly improved the component's accessibility and flexibility, providing RTL/LTR language support out of the box.
This blog post is not a tutorial on how to build an input counter component, but rather a discussion on how to structure the component for accessibility and good practice.
The problem of overlapping elements
We recently encountered an input counter component that failed the accessibility test performed by Evinced Web Flow Analyzer. The reported error was "There is an overlapping between interactive elements" involving the increment button
and the input
field. This issue surprised us because, at first glance, the component appeared accessible, with keyboard navigation, voiceover, and focus state working as expected during manual testing.
To investigate, we examined the component's implementation. Initially, its HTML template seemed well-structured, with the increment button
, input
field, and decrement button
nested as sibling elements within a div
container:
class="container">
type="number" class="input" aria-label="Counter value" class="counter" />
However, the component's CSS styling revealed a different story. The increment and decrement buttons were absolutely positioned, while the input
field was styled with width: calc(100% - 60px)
and horizontal padding (padding-inline: 30px
), aligning with the button widths:
.container {
/**... */
width: 150px;
position: relative;
}
.btn {
position:absolute;
height: 100%;
min-width: 30px;
}
.incr {
left: 1px;
}
.decr {
right: 0;
}
.counter {
border:none;
padding: 5px;
padding-inline: 30px;
width: calc(100% - 60px);
text-align: center;
}
While visually effective, this CSS introduced poor positioning practices:
- Using
position: absolute
removes buttons from the normal document flow, forcing the browser into additional layout computations. - Button positions aren't relative to the
input
field, causing additional complexity in CSS rules when adjusting font sizes, widths, or container dimensions. - Absolute positioning complicates RTL/LTR language support (such as Arabic or Hebrew), requiring extra CSS adjustments.
- There is no actual necessity for absolute positioning since elements are already correctly structured in HTML.
Additionally, the left: 1px
rule caused a 1px
overlap of the increment button
on the input
field, directly triggering the accessibility error.
Fixing overlapping and RTL issues using CSS Flexbox
A simple fix would be adjusting left: 1px
to left: 0
, removing the immediate overlap. However, this would only patch the bug without addressing the root issue (position: absolute
).
A better solution is using CSS Flexbox for the container, eliminating absolute positioning entirely. The updated CSS rules remove unnecessary properties (left
, right
, height: 100%
, padding
, and calc()
):
.container {
border: 1px solid lightgray;
padding:0;
display: flex;
}
.btn {
min-width: 30px;
}
input {
border:none;
padding: 5px;
text-align: center;
}
However, applying Flexbox introduces an overflow issue for the input
field, as form elements inherently have a default width
set by browser stylesheets.
Resolving input overflow in Flexbox
To address the overflow, set min-width: 0
and flex: 1
on the input
field. This ensures it dynamically shrinks or grows within the available container space:
input {
/** ... */
min-width: 0;
flex: 1;
}
Alternatively, using a fieldset
instead of a div
container could omit the min-width: 0
property. This provides a semantically meaningful solution beneficial for accessibility.
Supporting RTL/LTR languages
With Flexbox, the component inherently supports RTL/LTR languages without additional CSS rules. To verify this, use the dir
attribute on the container:
class="container" dir="rtl">
type="number" class="input" aria-label="Counter value" class="counter" />
This approach automatically swaps the increment and decrement buttons appropriately, maintaining a centered input field.
Summary
In this blog post, we addressed accessibility issues caused by overlapping elements in an input counter component due to absolute positioning. By replacing this with CSS Flexbox and adjusting the CSS style of input
field, we significantly improved the component's accessibility and flexibility, providing RTL/LTR language support out of the box.