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:
<div class="container">
<button aria-label="Increment value" class="btn incr">+</button>
<input type="number" class="input" aria-label="Counter value" class="counter" />
<button aria-label="Decrement value" class="btn decr">-</button>
</div>
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:
<div class="container" dir="rtl">
<button class="increment" aria-label="Increment value" class="btn incr">+</button>
<input type="number" class="input" aria-label="Counter value" class="counter" />
<button class="decrement" aria-label="Decrement value" class="btn decr">-</button>
</div>
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.
👉 Learn about Vue 3 and TypeScript with my new book Learning Vue!
Like this post or find it helpful? Share it 👇🏼 😉
Top comments (5)
Great article @mayashavin 👏
I have 2 questions:
Why are we using two class attribute ?
How does input and inc & dec button gets linked ? (I have an assumption here that accessiblity should link increment and decrement buttons with the input. Please let me know if my assumption is wrong)
If you use absolute position approach then you will need two class attributes, one for increment and one for decrement buttons. Otherwise, one class “btn” can be enough for both.
Not sure about the linking thing, can you elaborate what you mean?
This is code snippet from your blog. Here two class attribute is used. Probably first one doesnt have any significance. Please update the code snippet. (Earlier I thought it would be vue.js functionality).
Second question:
I have an assumption. So, please share your opinion on my assumption and if it feels right then move to the question part.
Assumption: For Accessiblity the input and buttons should be linked signifying the relation between input and buttons to the screen readers.
Question: Only if above assumption is true, How is this linking of input and buttons take place ?
the first class attribute doesn't do anything, it was a typo, and browser won't take it afaik. But good catch on that. Will update my snippet.
You can link input and buttons using WAI-ARIA attributes, and that's a different blog post, to give more context for the screen readers.
pretty cool seeing fixes that make stuff actually better for everyone - you think keeping things accessible like this really comes down to paying more attention to the basics or is it more about sticking with the newest tools