Skip to content

Floating labels in RTL forms

Fix floating labels in RTL forms by separating label direction, field state and text spacing, including autofill and left-to-right values.

By , Founder of Ritla

Guide13 min read

A floating label can move to the correct corner when an Arabic field receives focus and still cover the value when the field loses focus. Another version works when someone types but overlaps a saved value loaded before the first interaction. These are different defects: one concerns geometry, the other concerns state.

Fix both before calling the component RTL-ready. Logical positioning can anchor the Arabic label to the intended edge. It cannot tell the component that an autofilled or restored field already contains text. Translating the label cannot make its smaller position large enough for the actual Arabic wording either.

This guide uses a public-art inventory form. Its artwork-title field accepts an Arabic title such as ظلال الساحة; a separate inventory-reference field accepts an LTR identifier. Their labels remain Arabic even though the editable values have different direction policies.

Keep a real label before adding movement#

The floating text must remain the input's label in every visual state. A placeholder that moves or disappears is not an equivalent association.

html
<div class="art-field art-field--floating" lang="ar" dir="rtl">  <div class="art-field__control">    <label for="artwork-title">اسم العمل الفني</label>    <input id="artwork-title" name="artworkTitle" type="text" dir="rtl"           placeholder=" " aria-describedby="artwork-title-help">  </div>  <p id="artwork-title-help">اكتب الاسم كما سيظهر في دليل الأعمال.</p></div>

The for value matches the actual input ID. The help is associated separately, and neither association depends on the label's position. MDN's label reference explains explicit association and label activation.

The single-space placeholder is a state hook for the CSS example below. It is not the label, an instruction or an example value. If the product needs a format example, put it in visible help instead of competing with the label in the same space.

Do not duplicate the visible label in an unrelated aria-label. That can create two wording sources that drift after translation or replace the intended accessible name. W3C's form labelling guidance describes the relationship between visible labels and labelled controls.

Click the label in both its resting and raised positions. It should still activate the intended input. Inspect wrappers, overlays and pointer-event rules if only the input border receives the click while the real input remains unfocused.

Define when the label must stay raised#

A useful floating-label rule is that the label stays out of the editing line whenever the input has focus or contains a value. Filling and validation are separate states.

Field stateRequired label behavior
Empty and unfocusedMay rest inside the field if space permits
Empty and focusedRaised, leaving the editing line clear
Filled and unfocusedRaised so the saved value remains readable
Filled and invalidRaised; the correction belongs in associated feedback
AutofilledRaised without requiring a typed character
Prefilled and read-onlyRaised so the existing value can be reviewed
Cleared and unfocusedMay return to the empty resting state

An implementation that only raises on focus fails the filled-and-unfocused case. One that raises on :valid fails for a nonempty invalid value, and can raise an optional empty field even though nobody entered anything. Validity is not an emptiness detector.

Also avoid [value] as a selector for current contents. It tests an HTML attribute, not whether the live editor contains text. The value property represents the current value; a value restored through application state need not appear as an updated HTML attribute in the Elements panel.

For the text input above, :placeholder-shown provides a browser-owned presentation state. Its usefulness depends on the input supporting a placeholder and having one. Do not generalize that technique to every date, select or custom control because all of them share a visual field wrapper.

Do not substitute :empty. It concerns an element's child content, not the string being edited in an input. A selector can therefore match the wrong concept even when its name sounds exactly like the state the component needs.

Treat the placeholder hook as part of the component contract. Removing the space because it looks like unused content changes which state selector can match. Replacing it with a visible format example introduces a second piece of text into the resting label's space. Both changes need a component review, rather than being treated as harmless copy edits.

Include unusual but permitted values in the state review. A title containing only spaces is still visually different from an empty editor, even if validation later rejects it. Keep its label clear while showing the correction. Do not silently trim or rewrite the live value merely to make a floating-state flag easier to calculate.

Anchor the label using the label's interface direction#

Consider these declarations on the title label:

css
/* Problem: the label keeps the same physical corner in both locales. */.art-field__control label {  position: absolute;  left: 1rem;  top: 1.25rem;  transform-origin: left top;}

The product intends the Arabic label to begin at the form's inline start, which is the right edge in horizontal RTL layout. A physical left offset does not express that relationship. Adding dir="rtl" to the input changes its text direction; it does not rewrite the label's positioning declaration.

For that intended anchor, replace the physical offset with a logical one, remove the old offset and keep the label's direction connected to the interface. MDN documents the mapping of inset-inline-start. In horizontal LTR layout, inline start preserves the original left-edge intention; in RTL, it selects the right edge.

Do not mechanically rename every left as start. An element deliberately anchored to the English component's end has a different relationship. Establish which edge the design actually means before converting the declaration.

The supplied layout measurements used Chromium 151 on September 24, 2026; Firefox and Safari were not measured. Their relevant finding here is that physical offsets and transforms do not mirror merely because direction changes. Test the complete label movement in the browsers you support, including both interface directions.

Use a persistent-label fallback and opt into floating#

A persistent label is the fallback when selector support or available space cannot support the floating design. Start with that layout using the same markup:

css
.art-field__control label {  display: block;  margin-block-end: 0.4rem;} .art-field__control input {  inline-size: 100%;  box-sizing: border-box;  font: inherit;  padding-block: 0.9rem;  padding-inline: 1rem;}

Then add this enhancement after the base styles, replacing the earlier problem rule entirely:

css
@supports selector(.art-field__control:has(input:placeholder-shown)) {  .art-field--floating .art-field__control {    position: relative;  }   .art-field--floating .art-field__control label {    position: absolute;    inset-inline: 1rem;    inset-block-start: 0.35rem;    margin: 0;    font-size: 1rem;    line-height: 1.8;  }   .art-field--floating .art-field__control input {    padding-block: 2.25rem 0.75rem;    line-height: 1.8;  }   .art-field--floating .art-field__control:has(    input:placeholder-shown:not(:focus)  ) label {    inset-block-start: 1.25rem;  }}

The default enhanced position is raised. Only an unfocused input currently showing its placeholder lowers the label. A nonempty field stays raised without an event listener trying to remember whether typing occurred. :has() lets the wrapper respond to its input's state, while @supports limits the enhancement to the selector support it needs.

The label has space above the editing line and does not shrink. This example deliberately uses a short, single-line label. Its padding values are a starting geometry for that fixture, not a measurement of every Arabic translation or font. Opt in with art-field--floating only where the label fits in its allocated band; remove that modifier for the persistent layout when the content or layout needs more room.

The example avoids a border notch and horizontal translation. If your design includes either, test their geometry separately. A white rectangle behind a label can conceal a border in one theme and become a visible patch against an autofill background or another surface.

Treat transforms and their origin as separate decisions#

Logical positioning does not make translateX() logical. A label anchored at the right can still travel left or right according to the transform's numeric sign. If horizontal movement is unnecessary, remove it rather than layering a second directional correction over it.

Scaling has another anchor: transform-origin. MDN's transform-origin reference describes the point around which transforms operate. A label scaled around a physical left origin can appear to pull away from an intended right anchor.

If an existing design retains scaling, use explicit origins that match its intended anchor:

css
.scaled-art-label {  transform-origin: left top;} .scaled-art-label:dir(rtl) {  transform-origin: right top;}

This fragment applies to an existing scaled label whose LTR anchor is left and RTL anchor is right. It changes the origin, not the label's position or the condition that applies its scale. It is not needed by the unscaled example above.

The direction selector must match the label's interface direction. An LTR reference input does not make its Arabic label LTR. Also inspect whether the application declared direction with HTML or only CSS: the supplied measurements distinguish a CSS direction: rtl declaration from the semantic direction used by :dir(rtl).

Avoid scaleX(-1) on the field or label. Mirroring the geometry also mirrors the text. The goal is to preserve readable Arabic at the intended edge, not transform the label into a reflected image.

Measure a border notch independently of the label anchor#

If the design cuts a gap in the input border around the raised label, correcting the label's edge does not automatically correct the gap. The notch has its own position and width. An Arabic label can be right-anchored while the gap remains on the left, or while a width calculated for the English text cuts through an Arabic word.

Inspect which element the implementation measures. In the unscaled example, the label box spans the available inline space because both inline insets are set. Its full box width is not the width of the words inside it. Using that measurement as a notch width would open a gap unrelated to the text.

For a notched design, measure the intended text wrapper and define whether the measurement is a layout size or a transformed display size. MDN's getBoundingClientRect() reference explains the reported element rectangle. Do not mix measurements from different boxes simply because they return numbers in pixels.

Refresh any text-dependent measurement when translation or font metrics change. Then check both ends of the gap and the Arabic marks above and below the label. If maintaining the notch requires hiding text or fixing its width to one translation, remove the notch or use the persistent label instead.

Keep an Arabic label independent of an LTR value#

The inventory-reference field illustrates the difference between interface content and editable data. Its label can remain Arabic at the RTL wrapper's start while the identifier uses LTR editing.

html
<div class="art-field art-field--floating" lang="ar" dir="rtl">  <div class="art-field__control">    <label for="artwork-reference">مرجع السجل</label>    <input id="artwork-reference" name="artworkReference" type="text"           dir="ltr" placeholder=" " value="ART-Q9-673">  </div></div>

The CSS reads the input's placeholder and focus state, while label geometry comes from its RTL wrapper and its own inherited direction. A value does not need Arabic direction merely because its label is Arabic.

In this example the value is present at initial render, so the label must start raised. Check the first displayed frame as well as the state after focus. Raising only after an effect or blur can briefly expose a collision that a final screenshot misses.

Test a blank LTR reference field too. A resting Arabic label begins from a different edge than the future LTR value, but it must leave the editing line before the user needs that space. If the mixture is confusing for the product's audience, keep the label above the field permanently. Floating is a presentation choice, not a requirement for localizing a form.

For broader direction rules, use nested LTR content inside an RTL page. Do not solve this component by forcing the whole Arabic form LTR.

Test autofill and restored values without relying on typing#

Browser autofill, saved records, history restoration and application draft loading can all populate a field without following the same event sequence as typing. A label state that exists only inside a keyup handler misses those entry paths.

The CSS example uses the input's placeholder presentation, so ordinary nonempty text values remain in the raised-label branch. Test actual autofill as a separate path rather than treating a script assignment as equivalent to the browser's autofill behavior. MDN's :autofill reference explains that browser-owned state and styling.

Inspect label contrast against autofill colors. A transparent or patched background that looked correct against the empty field may no longer match the filled control. Avoid erasing browser feedback or the visible value just to make a label notch resemble the design mockup.

For a framework component, derive any application-owned filled state from the current value, including the initial state. Do not maintain a second “has been typed into” flag and assume it describes content. Resetting the form or loading another artwork must update the same state used for positioning.

Restoration also tests timing. Load a nonempty record, render it, then focus and blur without editing. Clear it, leave the field and restore it again. That sequence distinguishes a label following the actual contents from a label following an old interaction flag.

Pay attention to the period before application initialization completes. If server-rendered markup already includes the artwork title, the initial label position should agree with that value before event listeners or framework effects run. A later correction does not prevent the earlier overlapping frame from being displayed.

If the value is still loading, make the loading policy explicit. Keep the persistent label while the field's state is unknown, or render a loading presentation that cannot be mistaken for an editable empty record. Do not initialize a remembered "empty" flag and let it disagree with the value when the record arrives.

Test changing records without remounting the entire form. Move from a filled artwork to a blank record, then back to a filled one. The component should derive each label's position from the current control state, rather than carrying the previous record's focus or filled flag forward. Check history navigation as another entry path, because restoration need not follow the same sequence as opening a new page.

Reserve space for Arabic text rather than shrinking it away#

A floating design has at least two label positions to fit, and sometimes two font sizes. Evaluate both with the complete Arabic wording. The raised position is not exempt from readability because it occupies less space in a mockup.

The example keeps the label at the same font size and moves it vertically. If another design reduces the size, measure the resulting readable text size and line box instead of assuming the original font-size still describes what the user sees after a transform.

Include Arabic marks and actual font loading in the review. Test with the chosen webfont, then its fallback while the font is unavailable or still loading. A label fitting in one font can wrap or collide in another. Do not hide overflow on the whole field wrapper to conceal the extra line; it can also clip the label or focus indication.

Long labels need a layout decision. A two-line label cannot be accommodated by a band calculated for one line just by switching to logical properties. Increase the reserved space with a verified layout or use the persistent label. The static fallback is part of the component's design, not a failed translation.

Resize text and test narrow layouts with those real labels. W3C's Resize Text guidance explains the need to retain content and functionality when text grows. A component that passes at default zoom can still place a raised label over the value when the available inline space changes.

Keep help and validation outside the animation#

The label identifies the field; help explains how to fill it; an error describes what needs correction. Floating should not merge those roles into one moving sentence.

Keep help and validation in normal flow below the control, with the appropriate description association. An error can wrap without changing the label's anchor or covering the next field. Preserve the persistent help association when adding an error ID rather than replacing all previous descriptions.

Do not float only valid values or lower the label when validation rejects the title. The rejected text still occupies the input. A nonempty invalid value needs the same clear editing line as a valid one.

Check disabled and read-only filled fields in their own states. Someone reviewing a saved artwork still needs both its label and value. Reducing opacity does not create room for overlapping text, and hiding the label can make the value's purpose unclear.

If a clear button, prefix or status icon appears inside the control, account for that element separately. A floating label moving above the text does not reserve horizontal space for another action. Padding-inline for RTL layouts helps trace which element should own that reservation.

Add motion only after the stationary states fit#

Verify the resting and raised positions with transitions disabled. If either state overlaps the value, animation merely hides the defect for part of its duration.

The implementation above deliberately needs no animation. If you add a transition, target the property that actually changes rather than using transition: all. Check intermediate frames too: a label can cross a value while moving between two non-overlapping positions.

Honor the user's motion preference if the component animates. MDN documents prefers-reduced-motion. Removing movement should still leave a correctly positioned visible label; it must not leave the field waiting for an animation-completion callback to set its usable state.

Keep focus indication independent of the label transition. Focus is an interaction state that users need to locate immediately. It should not become visible only after the label reaches its final position, and it should remain on the real control rather than being replaced by decorative movement.

Review the component through state transitions#

Use the artwork title and inventory reference to exercise the same component in Arabic and LTR editing modes. Then repeat with the interface itself LTR, checking that the logical conversion preserves the intended English-side anchor.

TransitionWhat to inspect
Empty field gains focusLabel leaves the editing line before text needs it
Filled field loses focusLabel stays raised and the value remains readable
Nonempty value fails validationError appears without lowering the label
Saved value renders initiallyLabel starts in its filled position
Browser autofills the controlValue, label and autofill background remain readable
Clear, blur and restorePosition follows current contents rather than interaction history
Read-only or disabled record opensBoth label and value retain their meaning and space
Text grows or font changesLabel fits or the component uses its persistent layout

In DevTools, inspect the wrapper's direction, the label's inset and transform origin, the input's direction and the matched state selector separately. Correcting one does not establish the others. Disable transforms to expose an incorrect anchor, then disable transitions to inspect the two stationary positions.

Verify label activation, keyboard focus and the accessible name in every state. A screenshot can show spacing while missing a broken association. Conversely, a correct label association does not prove that the Arabic text is visible at a narrow width. Test the interaction and geometry together.

A free Ritla scan can report R041: Visible text-bearing elements overlapping, and R030: Direction-sensitive physical properties without an RTL override. For a raised label that still gets cut off, Arabic text getting cut off in CSS is the next place to check the containing box and line geometry.

Checks in this guide

See what your Arabic pages are hiding

Paste a URL. Ritla renders the page on desktop and mobile, runs every check, and shows the top issues with screenshot evidence.

Scan a page freeBrowse the checks