Welcome to the third and final post in the UX series. If you missed earlier articles, you can always go back and read "UX through a Frontend Developer’s eyes - practical UX protips for FEDs" or "Performance as UX (Perceived Performance)".
All the interactions discussed so far need to work across devices - and on mobile, they face a different set of constraints. More than half of all web traffic comes from mobile devices, yet many developer-built interfaces are designed cursor-first and touch-optimized as an afterthought. Mobile isn’t a smaller desktop - it has its own interaction model, constraints, and opportunities.
Thumb zone and reachability
On a phone held in one hand, the user’s thumb can comfortably reach only a limited arc of the screen - roughly the lower-center area. The top corners and far edges require stretching or a second hand. This has direct consequences for where you place primary actions.
- Place primary actions in the bottom half - navigation bars, FABs (floating action buttons), and key CTAs belong in the thumb’s natural arc. iOS and Android both moved their primary navigation to the bottom of the screen years ago for exactly this reason.
- Avoid top-corner actions for frequent tasks - a hamburger menu in the top-left corner requires the most uncomfortable reach on a phone. If it’s the primary way to navigate your app, consider a bottom tab bar or a bottom sheet instead.
- Size touch targets generously - as mentioned in Fitts’s Law, 44x44px (Apple) or 48x48px (Google) is the minimum. On mobile, this isn’t a suggestion - it’s the difference between usable and frustrating.
Swipe gestures and bottom sheets
Touch interfaces support gestures that have no cursor equivalent. Swipe-to-delete, pull-to-refresh, and swipe between tabs are patterns users already know from native apps - and they expect them on the mobile web too.
- Swipe actions on list items - swipe left to delete, swipe right to archive. These reduce the number of taps needed and feel natural on touch devices. Always provide a non-swipe alternative (a button or menu) for discoverability.
- Bottom sheets instead of modals - on mobile, a modal centered on the screen wastes the top half (it’s unreachable) and forces the user to reach up to close it. A bottom sheet slides up from the bottom, keeping all interactive elements in the thumb zone.
- Pull-to-refresh - for content feeds and lists, pulling down to refresh is an expected gesture. If your app shows live or frequently-updated data, implement it rather than relying on a reload button.

UX through a Frontend Developer’s eyes
Download full guideAccessibility is part of UX
Mobile is one dimension of adapting to different users and contexts. Accessibility broadens this to all users, regardless of ability. A significant group of users experiences the web differently from you.
- People with visual impairments use screen readers that parse the DOM and read content aloud.
- People with motor disabilities navigate entirely with a keyboard or switch device - no mouse, no gestures.
- People with cognitive disabilities benefit from clear structure, consistent navigation, and predictable behavior. Color-blind users can’t rely on color alone to distinguish states.
If your interface only works for sighted mouse users, it doesn’t work. Accessibility isn’t a nice-to-have checkbox - it’s a core part of UX quality. And in the EU, it’s also a legal requirement - the European Accessibility Act (EAA) mandates that many digital products and services meet accessibility standards starting from June 2025. And much of it comes down to writing proper HTML.
Semantic HTML instead of div soup
The single biggest accessibility win is also the simplest: use HTML elements for their intended purpose.
A <div> with an onclick handler looks like a button on screen, but to a screen reader, it’s just an anonymous container. It’s not focusable by default, it doesn’t respond to Enter or Space keypresses, and it doesn’t appear in the accessibility tree as an interactive element. The user literally doesn’t know it’s there.
The fix is not to bolt ARIA onto a div to make it behave like a button. The fix is to use <button>:
This applies across the board:
- Navigation - use
<nav>and<a>for links, not styled divs with click handlers. Screen readers expose<nav>as a landmark that users can jump to directly. - Lists - use
<ul>/<ol>and<li>for lists. Screen readers announce “list, 5 items” - giving users context before they dive in. - Forms - use
<form>,<label>, and<input>with properfor/idbinding. A<label>connected to an input viaformeans that clicking the label focuses the input (a larger hit area), and screen readers announce what the field is for. - Sections - use
<main>,<header>,<footer>,<aside>,<section>to create landmark regions. Screen reader users navigate by landmarks, the way sighted users scan by visual layout. - Tables - use
<table>,<thead>,<th>,<tbody>for tabular data. Screen readers let users navigate cell-by-cell and announce row/column headers. A grid of styled divs loses all of that.

Heading hierarchy
Screen reader users often navigate a page by jumping through headings - the way a sighted user scans by scrolling and reading large text. If your headings skip levels or are used for styling rather than structure, this navigation breaks.
Rules:
- One
<h1>per page - the page title. It’s the first thing a screen reader user hears. - Don’t skip levels - go
h1->h2->h3, noth1->h3. Skipping creates gaps in the heading outline, confusing navigation. - Don’t use headings for styling - if you need text to look like a heading but it’s not a structural heading, style a
<p>or<span>with CSS. Don’t abuse<h3>just because you like its default font size. - Every content section should start with a heading - it acts as a navigation anchor for assistive technology users.
You can audit your heading structure with browser devtools. Chrome’s accessibility tree view or extensions like HeadingsMap show you the outline as a screen reader would interpret it.
ARIA - when native HTML isn’t enough
ARIA (Accessible Rich Internet Applications) attributes add semantics to elements when native HTML doesn’t cover the interaction pattern. But the first rule of ARIA is: don’t use ARIA if a native HTML element can do the job. ARIA doesn’t add behavior - it only adds labels to the accessibility tree. A <div role="button"> is announced as a button, but still isn’t focusable or keyboard-operable without extra JavaScript.
When ARIA is the right tool:
aria-labelandaria-labelledby- provide accessible names for elements that don’t have visible text. An icon-only button needs anaria-label="Close"because screen readers can’t see the X icon.
aria-liveregions - announce dynamic content changes. When a toast appears, or a form validation message shows up, sighted users see it instantly - but screen reader users only know about content that’s announced.aria-live="polite"waits for the screen reader to finish its current announcement,aria-live="assertive"interrupts immediately (use sparingly).
aria-expanded- for toggleable elements like accordions, dropdowns, and collapsible sections. Tells screen reader users whether the associated content is visible.
aria-invalidandaria-describedby- for form validation. Mark invalid fields and point to their error message so screen readers read both the field label and the error.
role="alert"- immediately announces content to screen readers. Use for error messages and critical notifications that the user must hear right away.
Hiding elements accessibly
Not all hiding is equal. The CSS property you choose determines whether an element is hidden only visually, or also from assistive technology:
| Method | Visible | In layout flow | Announced by screen readers |
display: none | No | No | No |
visibility: hidden | No | Yes (reserves space) | No |
opacity: 0 | No | Yes | Yes |
(below) | No | No | Yes |
Both display: none and visibility: hidden hide content from screen readers entirely. This is correct for a UI that is truly inactive (collapsed menus, unopened modals). But sometimes you need content that is invisible on screen yet still available to assistive technology - for example, a descriptive label for an icon button, or a “skip to content” link that only screen reader and keyboard users need.
The standard pattern for this is .visually-hidden (sometimes called .sr-only):
This clips the element to a 1px area offscreen - invisible to sighted users, but still present in the accessibility tree and announced by screen readers. Use it for:
- Skip navigation links - “Skip to main content” links that appear on focus (add
:focusstyles to make them visible when tabbed to). - Icon-only button labels -
<button><svg>...</svg><span class="visually-hidden">Close</span></button>as an alternative toaria-label. - Form instructions - additional context for screen reader users that would be visually redundant.
Keyboard navigation
Every interactive element must be operable with only a keyboard. This means:
- All clickable elements must be focusable - native
<button>,<a>, and<input>are focusable by default. If you use a<div>or<span>as a trigger (don’t), you needtabindex="0"to add it to the tab order and keyboard event handlers for Enter and Space. - Tab order must follow visual order - users expect Tab to move left-to-right, top-to-bottom. If your CSS layout (flex
order, grid placement, absolute positioning) visually reorders elements, the tab order still follows the DOM order. Keep DOM order aligned with visual order. - Trap focus in modals - when a modal opens, pressing Tab should cycle through only the modal’s interactive elements, not escape to the page behind the overlay. When the modal closes, return focus to the element that opened it.
- Provide visible focus indicators - as mentioned in the component states section, never remove
:focusstyles without providing a visible alternative.:focus-visibleis the modern way to show focus only for keyboard users (not for mouse clicks).
Practical checklist
Some quick wins you can apply right now:
- Add
alttext to all meaningful images. Decorative images getalt=""(empty, not missing) so screen readers skip them. - Don’t rely on color alone - pair color coding with icons, text, or patterns. A form field with a red border also needs an error icon or message.
- Test with keyboard only - put your mouse in a drawer for 5 minutes and Tab through your app. You’ll find problems in seconds.
- Check contrast ratios - WCAG requires 4.5:1 for normal text and 3:1 for large text. Chrome DevTools shows contrast ratios in the color picker. Fix any that fail.
- Use the
langattribute on<html>- screen readers use it to select the correct pronunciation engine.<html lang="en">is one line and makes a real difference. - Test with a screen reader - macOS has VoiceOver built in (Cmd+F5). Spend 10 minutes navigating your own app with it. It’s the fastest way to find what’s broken.
Closing thoughts
Modern tools dramatically accelerate UI creation, but interface quality still depends on implementation details: how progress is shown, how actions respond, how motion behaves, and how stable layouts remain during change.
These small technical decisions compound into a noticeably better user experience - and they’re fully within reach at the frontend implementation level. You don’t need a design degree or a UX certification.
You need to care about the details that users feel but rarely articulate, and to treat every loading state, every transition, and every error as an opportunity to make your interface feel like it was crafted with intention.
Special thanks to Jakub Łyko, Łukasz Pyrzyński, Marcin Baraniecki, Dariusz Jaroń for their feedback and peer review of this blog post.



