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.

Apr 16, 2025 - 08:50
 0
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

Counter component

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">
   class="increment" aria-label="Increment value" class="btn incr">+
   type="number" class="input" aria-label="Counter value" class="counter" />
   class="decrement" aria-label="Decrement value" class="btn decr">-

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:

  1. Using position: absolute removes buttons from the normal document flow, forcing the browser into additional layout computations.
  2. Button positions aren't relative to the input field, causing additional complexity in CSS rules when adjusting font sizes, widths, or container dimensions.
  3. Absolute positioning complicates RTL/LTR language support (such as Arabic or Hebrew), requiring extra CSS adjustments.
  4. 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.

Counter component with an overflowed input

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">
   class="increment" aria-label="Increment value" class="btn incr">+
   type="number" class="input" aria-label="Counter value" class="counter" />
   class="decrement" aria-label="Decrement value" class="btn decr">-