This episode covers ARIA roles, keyboard navigation, and focus management, semantic HTML and accessible forms, mobile-first responsive design, and the basics of internationalization or i18n for multilingual apps.

An inaccessible app closes the door on millions of users — those who use screen readers, keyboards only, or small screens. Episode 17 makes accessibility (a11y) and UX part of the design, not an addition at the end.
We'll cover ARIA roles, keyboard navigation and focus management, semantic HTML with accessible forms, mobile-first responsive design, and the basics of internationalization so your app reaches users across languages.
ARIA (Accessible Rich Internet Applications) describes the roles and states of elements that lack built-in HTML semantics. For example, a tab panel:
function Tabs({ tabAktif, gantiTab }) {
return (
<div role="tablist">
<button
role="tab"
aria-selected={tabAktif === "profil"}
aria-controls="panel-profil"
onClick={() => gantiTab("profil")}
>
Profil
</button>
<div id="panel-profil" role="tabpanel" hidden={tabAktif !== "profil"}>
Konten profil
</div>
</div>
)
}role="tab" and aria-selected tell the screen reader that the button is a tab and which one is active. aria-controls connects the tab to its panel. Important rule: if a semantic HTML element exists (button, nav, dialog), use it before ARIA.
Keyboard users must be able to reach everything. Two main points of attention:
outline: none) without replacing it with a clear focus indicator.import { useEffect, useRef } from "react"
function Modal() {
const refTombol = useRef(null)
useEffect(() => {
refTombol.current.focus()
}, [])
return (
<div role="dialog" aria-modal="true" aria-labelledby="judul-modal">
<h2 id="judul-modal">Konfirmasi</h2>
<button ref={refTombol}>Setuju</button>
</div>
)
}refTombol.current.focus() moves focus to the main button when the modal renders, so keyboard and screen reader users are immediately in the right place.
Use elements whose meaning is already known to screen readers:
nav for navigation, main for main content, header and footer.button for actions, not a div with onClick.label always connected to the input with htmlFor.<form>
<label htmlFor="email">Alamat email</label>
<input id="email" type="email" aria-describedby="email-hint" />
<p id="email-hint">Kami tidak akan membagikan email kamu.</p>
</form>aria-describedby="email-hint" connects the input to helper text, which the screen reader reads when the input is focused. Accessible forms also use aria-invalid and role="alert" for error messages as in episode 11.
Mobile-first means designing for small screens first, then scaling up with media queries. Default sizes fit narrow screens; min-width adds styles for larger screens:
.kartu {
padding: 12px;
font-size: 14px;
}
@media (min-width: 768px) {
.kartu {
padding: 20px;
font-size: 16px;
}
}The @media (min-width: 768px) media query only kicks in on tablets and up. This approach forces you to prioritize what truly matters on small screens.
Build components that flex to their size. A common pattern: a grid that flows with auto-fit and minmax:
.grid-produk {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}repeat(auto-fit, minmax(200px, 1fr)) makes columns auto-adjust to the width: one column on phones, several columns on wide screens — without JavaScript.
An app that wants to go international needs internationalization: separating text from code. A popular library: react-i18next:
npm install i18next react-i18nextimport { useTranslation } from "react-i18next"
function SelamatDatang() {
const { t } = useTranslation()
return <h1>{t("selamatDatang", "Selamat datang!")}</h1>
}const { t } = useTranslation() returns the t function that translates text keys. Translation files hold key-text pairs per language, and users can switch languages without a reload.
Tip
Don't write text directly inside components for multilingual apps. Keep all text in translation files from the start — moving text that's already scattered is much harder than putting it in one place.
Episode 17 opened your app to everyone: ARIA roles and keyboard navigation, focus management, semantic HTML with accessible forms, mobile-first responsive design, and the basics of internationalization.
Key takeaways:
aria-invalid and role="alert" make forms accessible.min-width and flexible grids.In the next episode, episode 18, we'll cover architecture & design patterns — component-driven architecture and atomic design, feature modules with a scalable folder structure, composition patterns and render props, and design systems for consistent UI. Good architecture lets your team move fast.