A language selector sounds like a small job until you ship it on a page where every pixel matters. On ramrom.io I run a video downloader with nine languages and no framework anywhere — plain HTML, a bit of CSS, and vanilla JavaScript. The selector had to let a visitor switch language without the page jumping around, and it had to work on a cheap Android phone on a slow connection.

The layout shift problem

My first attempt was the obvious one: a <select> element in the header. It worked, but it looked wrong on every device. On desktop it was a grey box that clashed with the blue-and-white theme. On mobile it was wide enough to push the logo onto a second line, and when the language changed, the longer translated strings — German button labels are famously long — shoved the whole header sideways.

That sideways shove is a layout shift, and it is exactly the kind of thing that makes a tool page feel cheap.

What I built instead

The selector is now a plain <button> with a short label — the current language code, like EN — and a tiny chevron drawn in CSS. Clicking it opens a small list of languages positioned absolutely below the button. The list is a <div> with position: absolute, so opening it never moves anything else on the page. The button itself is a fixed width, wide enough for the longest label I ship, so switching from EN to العربية does not change its size either.

The translations live in a small JavaScript dictionary: one object per language, keyed by element IDs. Switching language is a loop that walks the dictionary and sets textContent on each element. There is no re-render, no framework diffing, and no network request — the whole dictionary ships inside one small script file, so the switch happens instantly even on a slow connection.

A few details that mattered more than I expected:

  • The button keeps its width. I set min-width on it and sized it for the longest label. A button that grows and shrinks every time you change language is a layout shift wearing a disguise.
  • The list closes itself. Clicking outside, pressing Escape, or picking a language all close it. A dropdown that stays open over the download button is worse than no dropdown at all.
  • The choice is remembered. I store the language in localStorage, so a returning visitor sees their language straight away instead of flashing English first and then switching. That flash of the wrong language is another kind of shift, just a visual one.
  • Right-to-left is a real case. Arabic is one of the nine languages, and the page needs dir="rtl" on the <html> element when it is active. The selector button stays put because it is positioned with logical properties rather than fixed left and right values.
  • No webfont for the labels. The language names render in the system font stack. Downloading a font just to spell out nine language names would be a performance cost with no benefit.

What I would do differently

Honestly, the <select> element is not a bad answer for most sites — it is accessible out of the box, and screen readers understand it perfectly. What I built needs the extra work: aria-expanded on the button, arrow-key navigation through the list, and focus returning to the button when the list closes. If you skip that, you have built a prettier control that some visitors cannot use at all.

The lesson I keep coming back to is a boring one: layout shift is not only about images without dimensions. Any element that changes size after the page loads — a language label, a button, a header — can do it. Fixing it usually means reserving the space up front instead of letting the browser discover the size later. That is true for a language selector, and it is true for everything else on a tool page where the visitor's attention is on one button and nothing should move around it.