← Back
[W] Writing

When a search input runs out of room

Most search inputs get designed empty. There's a placeholder, a little ⌘K on the right, and it looks great.

Then someone pastes a long query, and the text runs straight under the shortcut.

A real input, so type in it too. Turn off both rules to see where it started.

Two rules

Fade the end of the text when it doesn't fit. A hard edge looks like a mistake, because it looks like the text was cut off by accident. A fade says there's more of this, and it was meant to happen.

Hide the shortcut when it stops being useful. ⌘K tells you how to get to the input. Once you're focused in it, you've already arrived, so the hint has done its job. Hiding it also gives your text that space back.

Fade the text, not the input

My first version put the mask on the whole input, and it was wrong in two ways. It faded the input's border along with the text. And type="search" gives you a built-in clear button inside the input, so that faded too, into a smudge at the end of the text.

So the border belongs to a wrapper, and the input and the shortcut sit next to each other inside it. The mask goes on the input alone, so it can't touch anything else. The shortcut is outside it, and when the shortcut hides, the input simply gets wider.

.search {
  display: flex;
  border: 1px solid var(--rule);
}

.search input {
  flex: 1;
  min-width: 0;
  border: 0;
  mask-image: linear-gradient(
    to right,
    transparent,
    #000 var(--start),
    #000 calc(100% - var(--end)),
    transparent
  );
}

/* Or give it a clear button you control, outside the mask. */
.search input::-webkit-search-cancel-button {
  display: none;
}

Only fade where there's more text

A fade on short text looks like a rendering bug. And a fade on the right while you're typing is worse, because it hides the letters you just typed. So each side only fades if there's more text in that direction.

const before = input.scrollLeft > 0;
const after =
  input.scrollLeft + input.clientWidth < input.scrollWidth;

search.style.setProperty("--start", before ? "2.5rem" : "0px");
search.style.setProperty("--end", after ? "2.5rem" : "0px");

Run it when the value changes, when the input scrolls, and when the caret moves. Click in the demo and press End to watch the fade swap sides.

It's also why I use mask-image here. A gradient placed on top of the input has to match the background colour exactly, and it breaks as soon as the input sits on a different surface. A mask only changes the text, so it works anywhere.

Why I like this one

This tiny detail got 442 likes and 65,000 views when I posted it, which makes it one of my most liked posts ever. I don't think that's because people care about search inputs. I think it's because everyone has seen this bug, and had stopped noticing it.

Every input has an empty state and a too-full state, and the second one rarely gets designed. Before you call an input finished, paste a paragraph into it.

Next