FormsFree

Underline Focus

A field reduced to a single line: on click, an indigo-to-magenta stroke starts in the middle and runs out to both edges while the label rises above.

CSSFocusGradient

Updated

Hover or click the scene to interact

This effect is free — the Forms category contains 20 effects total, including 2 free. Effect.Labs has 840 vanilla effects. Explore the category →

Usage examples

BrunelysFrench · Year 7Exercise 4 of 10

Put the verbs in brackets into the past tense.

Yesterday we to the cinema and we from start to finish.

① Fill-in-the-blanks verb exercise — Brunelys learning platform

WhenThe pupil reads "Yesterday we (go) to the cinema and we (laugh)…": each infinitive is the label of a blank. Clicking or pressing Tab lifts the verb above the blank and the coloured stroke shows where they are typing; "Check" marks each answer.
WhyA field reduced to one line melts into a sentence like the blank on a paper worksheet, and the rising label keeps the infinitive in view while the pupil conjugates.
Settings--udl-width: 128px on the root, fields switched to display: inline-block by the scene so they flow in the paragraph (Georgia 19 px, line height 1.9). The root doubles as the card, default background.
TessalyMy files › Clients
NameItemsModified
  • Brand guidelines14 items3 Nov
  • Quotes 202632 itemsYesterday
  • Workshop photos208 items28 Oct
  • Invoices57 items12 Oct

② Renaming a folder in place — Tessaly file manager

WhenClicking "Rename" swaps the row's name for the field, already filled and selected: the "New name" label is up from the start and the stroke opens under the name. Enter or a click elsewhere saves, Escape cancels.
WhyRenaming should not open a dialog: a frameless field keeps the row aligned with its neighbours, and only the stroke says it is being edited.
Settings--udl-width: 100%, one field per row shown only while editing, 62 px rows so the raised label fits without shifting the list. Root without background (the list has its own).

Maison Ferbaline · Bistro

A table for tonight?

Market cooking, 28 seats. Dinner from 7 to 10 pm, Tuesday to Saturday.

More than 8 guests? Call us.

③ Table booking on an editorial page — Maison Ferbaline

WhenBeside the bistro's introduction, three thin lines: name, date and time, guests, then the Book button. The stroke follows the visitor from line to line.
WhySolid boxes would break the magazine layout; plain lines suit the typography, and the gradient is enough to show where you are typing.
Settings--udl-width: 100%, transparent <form> root on a brown background, required fields (the browser blocks an empty line on submit), guests as type="number" from 1 to 8.

How it works

The .udl-input field has no fill and no frame, only a 2 px bottom rule in rgba(255, 255, 255, .2). Right after it inside the <label class="udl-field"> come the .udl-label text and an empty <span class="udl-line">, absolutely positioned on that rule: it starts at left: 50% with zero width.

When the field takes focus, .udl-input:focus ~ .udl-line moves the stroke to left: 0 and width: 100%. Both values change together over 0.4 s on a cubic-bezier(.4, 0, .2, 1) curve: the #6366f1 → #d946ef gradient seems to be born in the centre and open towards both sides. When focus leaves, the same transition closes it back to the middle.

The label follows a separate rule. It sits at text height (top: 30px) and rises to top: 2px, in indigo 0.75rem, as soon as :focus or :not(:placeholder-shown) applies; the invisible placeholder=" " is only there to tell whether the field holds anything. A filled field therefore keeps its label up after the coloured stroke has gone.

The padding-top: 18px on .udl-field reserves room for the raised label: stacked in a form, two fields never overlap. The .fx-0282-root root (dark background, 250 px minimum height) is only there for the demo.

Accessibility

  • Real label: the <label> wraps the field, so "Full name" is its accessible name, with no id to de-duplicate when the page holds several.
  • Form attributes: name="name" and autocomplete="name" are supplied; the browser offers the saved name and the value goes out with the form.
  • Spotting the active field: the coloured stroke only exists during focus. A filled field the user has left gets the grey rule back, which sets it apart from the one being edited.
  • Reduced motion: under prefers-reduced-motion: reduce, neither the stroke nor the label slides; both appear directly in their final place.
  • Contrast: at rest, #8b8b93 on rgb(26, 26, 36) reaches 5.1:1; the raised label, #818cf8 at 12 px, reaches 5.8:1. Measured in Chromium, Firefox and WebKit, at rest, focused and filled.

Browser compatibility

Uses :placeholder-shown (Chrome 47, Firefox 51, Safari 9), the ~ sibling selector, a linear gradient and CSS variables (Chrome 49, Firefox 31, Safari 9.1). Tested in Chromium, Firefox and WebKit: two fields side by side each animating on their own, Tab, touch, submission in a form, 390 px width, reduced motion.

Chrome 49+✓ Full (measured)
Firefox 51+✓ Full (measured)
Safari 9.1+✓ Full (WebKit engine measured)
Edge 79+✓ Full (Chromium engine)
Mobile iOS 9.3+✓ Full
Android Chrome 49+✓ Full (Chromium engine)

Without transitions, stroke and label jump from one state to the other and the field stays usable. Without :placeholder-shown, the label drops back onto the typed text when the field is left.

The code

Copy the two blocks (HTML and CSS) into your page. No JavaScript, no dependencies.

HTML
<div class="fx-0282-root">
  <label class="udl-field">
    <input class="udl-input" type="text" name="name" autocomplete="name" placeholder=" ">
    <span class="udl-label">Full name</span>
    <span class="udl-line" aria-hidden="true"></span>
  </label>
</div>
CSS
.fx-0282-root,
.fx-0282-root *,
.fx-0282-root *::before,
.fx-0282-root *::after {
  box-sizing: border-box
}

.fx-0282-root {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 250px;
  padding: 24px 16px;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.udl-field {
  position: relative;
  display: block;
  width: var(--udl-width, 280px);
  max-width: 100%;
  padding-top: 18px;
}

.udl-input {
  width: 100%;
  padding: 12px 0;
  font-size: 1rem;
  font-family: inherit;
  background: transparent;
  border: none;
  border-bottom: 2px solid rgba(255, 255, 255, 0.2);
  color: white;
  outline: none;
}

.udl-line {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 0;
  height: 2px;
  background: linear-gradient(90deg, #6366f1, #d946ef);
  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}

.udl-input:focus ~ .udl-line {
  left: 0;
  width: 100%;
}

.udl-label {
  position: absolute;
  left: 0;
  top: 30px;
  color: var(--udl-muted, #8b8b93);
  pointer-events: none;
  transition: all 0.3s ease;
}

.udl-input:focus + .udl-label,
.udl-input:not(:placeholder-shown) + .udl-label {
  top: 2px;
  font-size: 0.75rem;
  color: #818cf8;
}

@media (prefers-reduced-motion: reduce) {
  .udl-line,
  .udl-label {
    transition: none;
  }
}
JavaScript

Pure CSS — no JavaScript needed.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Width (CSS — --udl-width) 280 px 100% to follow the column, a short value (100 to 140 px) for a field dropped into a sentence. The field never exceeds its container (max-width: 100%).
Stroke gradient (CSS — background of .udl-line) linear-gradient(90deg, #6366f1, #d946ef) A plain colour works too. Keep a hue clearly brighter than the grey rule so the active field stands out.
Thickness (CSS — height of .udl-line and border-bottom of .udl-input) 2 px Change both values together: the stroke covers the bottom rule exactly.
Opening speed (CSS — transition of .udl-line) 0.4 s The label moves in 0.3 s. A stroke slightly slower than the label makes the line seem to follow the text going up.
Resting label colour (CSS — --udl-muted) #8b8b93 The raised label keeps #818cf8, hard-coded in the :focus + .udl-label rule.
Room above (CSS — padding-top of .udl-field) 18 px If you enlarge the raised label, increase this padding and the resting label's top (30 px) by the same amount.

FAQ

Why does the stroke start from the centre rather than the left?

Because two properties change at once: left goes from 50% to 0 while width goes from 0 to 100%. For a stroke starting on the left, keep left: 0 at rest and animate only the width.

Does the field work on a light background?

Typed text is white and the rule is a transparent white: on a light background, make the color of .udl-input dark and the rule rgba(0, 0, 0, .2). The indigo-magenta gradient stays readable as is.

Can a field sit in the middle of a sentence?

Yes: give .udl-field display: inline-block and a short width through --udl-width. The text line grows to hold the room reserved for the label; plan for a line height around 1.9.