padding-inline for RTL layouts
Use padding-inline, padding-inline-start and padding-inline-end to preserve internal spacing across LTR and RTL without mirrored overrides.
Guide13 min read
An alert can move its dismiss button to the correct side in Arabic and still put the text underneath it. The button was mirrored. The empty space reserved for it was not.
That empty space is often physical padding: extra padding-right for an end-side action, padding-left before a leading symbol, or a four-value padding shorthand written for the English layout. When direction changes, those declarations stay attached to the same physical edges. The content flow moves on without them, which is rude but entirely according to specification.
padding-inline, padding-inline-start and padding-inline-end describe internal spacing relative to the inline reading direction. In a horizontal LTR interface, inline start is left and inline end is right. In a horizontal RTL interface, start is right and end is left. The browser maps the declarations after it knows the element's direction and writing mode.
Use them when the spacing belongs before or after content. Keep physical padding when the design truly means a fixed left or right edge. Padding does not need ideological purity. It needs an accurate job description.
What padding-inline changes#
Padding sits between an element's content and border. It is inside the box, so the element's background paints through it and, on an interactive element, it contributes to the clickable area. This is different from margin, which creates space outside the border.
The logical inline properties map like this under writing-mode: horizontal-tb:
| Declaration | LTR mapping | RTL mapping |
|---|---|---|
padding-inline-start | padding-left | padding-right |
padding-inline-end | padding-right | padding-left |
padding-inline: 1rem | 1rem left and right | 1rem right and left |
padding-inline: 1rem 3rem | 1rem left, 3rem right | 1rem right, 3rem left |
The one-value shorthand is symmetrical, so changing direction produces no visual difference. It is still useful because it states that the value applies along the inline axis. The two-value form is where order matters: the first value is start and the second is end. It is not left then right wearing modern clothes.
MDN's padding-inline reference defines the shorthand and its one-value and two-value forms. The CSS Logical Properties specification defines the mapping from logical padding to physical sides using the element's writing mode, direction and text orientation.
In vertical writing modes, inline start and end can map to top and bottom. For the horizontal English and Arabic interfaces in scope here, they map to left and right. This distinction is easy to ignore until a shared component appears in vertical editorial content and starts collecting padding at the top. CSS does enjoy a technically justified surprise.
Reserve space on the same logical side as the control#
A clinical portal shows a result notice with a dismiss button at inline end. The notice needs more padding on that edge so its title and summary never run underneath the button.
The LTR implementation uses physical sides:
Problem
<aside class="result-notice">
<h2 class="result-notice__title">Lab result available</h2>
<p class="result-notice__summary">Your clinician has added a note.</p>
<button class="result-notice__dismiss" type="button" aria-label="Dismiss">×</button>
</aside>.result-notice {
position: relative;
padding: 1rem 3.75rem 1rem 1.25rem;
}
.result-notice__dismiss {
position: absolute;
top: 1rem;
right: 1rem;
}In LTR, the right padding reserves room for the right-positioned control. In RTL, both remain physically right. The component no longer mirrors with the reading flow.
A partial fix is worse. If the button changes to inset-inline-end but the notice keeps padding-right: 3.75rem, the button moves left in RTL while the reserved space remains right. A longer Arabic title can then extend under the button. Each declaration is locally plausible. Together they have stopped speaking.
Better
<aside class="result-notice">
<h2 class="result-notice__title">Lab result available</h2>
<p class="result-notice__summary">Your clinician has added a note.</p>
<button class="result-notice__dismiss" type="button" aria-label="Dismiss">×</button>
</aside>.result-notice {
position: relative;
padding-block: 1rem;
padding-inline: 1.25rem 3.75rem;
}
.result-notice__dismiss {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
}The LTR geometry is unchanged: 1.25rem on the left, 3.75rem on the right, and the button at the right. In RTL, the larger padding and the button both move to the left, which is inline end. The reserved space follows the thing it was reserving space for. This is a modest standard for cooperation, yet it fixes a surprising number of components.
Treat related directional declarations as a set. An end-side control may involve padding-inline-end, inset-inline-end, an end-side border and an icon whose direction may or may not mirror. Converting only the declaration currently causing the screenshot difference can create an overlap one breakpoint later.
Choose start and end from intent, not from the old side#
A mechanical conversion from padding-left to padding-inline-start is sometimes right. It is not a rule.
Imagine these LTR declarations:
.specimen-note {
padding-left: 1.5rem;
}
.result-notice {
padding-right: 3.75rem;
}
.chart-scale {
padding-left: 8px;
}The specimen note may use left padding because a start-side status marker sits before its content. That should become padding-inline-start.
The result notice uses right padding because an end-side control needs room. That should become padding-inline-end.
The chart scale may refer to a fixed physical left axis. That should remain padding-left. If the chart itself does not mirror, moving its coordinate padding would damage the relationship between the label and the data.
Write the intent in plain language before changing the property:
- “space before the content” becomes
padding-inline-start; - “space after the content” becomes
padding-inline-end; - “equal inline breathing room” becomes
padding-inlinewith one value; - “different start and end room” becomes
padding-inlinewith two values; - “space from the physical left edge” remains
padding-left.
This takes longer than global replacement and considerably less time than explaining why every Arabic banner has migrated to a different corner.
The same judgment applies to icons. If an icon participates in normal flex or grid layout, use gap for the space between it and the label. Do not manufacture that gap by adding extra padding to the whole button unless the button genuinely needs asymmetric internal space. Padding belongs between a box edge and its contents. gap belongs between layout children. They occasionally produce the same screenshot, which is how wrong abstractions obtain references.
Use the shorthand deliberately#
padding-inline sets two constituent properties: padding-inline-start and padding-inline-end.
One value applies to both:
.result-card {
padding-inline: 1.25rem;
}Two values apply to start, then end:
.result-notice {
padding-inline: 1.25rem 3.75rem;
}Longhands are clearer when only one edge changes between variants:
.result-notice {
padding-inline: 1.25rem;
}
.result-notice--dismissible {
padding-inline-end: 3.75rem;
}That variant leaves start padding alone and reserves extra room only at end. It also avoids repeating the base value, which is a small kindness to the next person changing the spacing token.
The four-value padding shorthand is still physical in current browser implementations. Its familiar order is top, right, bottom, left:
.result-notice {
padding: 1rem 3.75rem 1rem 1.25rem;
}Those right and left values do not swap under dir="rtl". If both axes need flow-relative spacing, use padding-block and padding-inline:
.result-notice {
padding-block: 1rem;
padding-inline: 1.25rem 3.75rem;
}The MDN guide to logical margins, borders and padding shows the wider property mapping. For a broader migration beyond padding, the RTL CSS guide covers positioning, borders, flex, grid and the other places physical assumptions settle in.
Watch the cascade when physical and logical padding meet#
Physical and logical padding declarations belong to the same property group. When two declarations map to the same side, the normal cascade decides the winner. When direction changes, the declarations may map to different sides and both may apply.
.result-notice {
padding-inline-end: 3.75rem;
padding-right: 1rem;
}In LTR, inline end maps to right. With equal cascade priority, the later padding-right wins, so the reserved space shrinks to 1rem.
In RTL, inline end maps to left. The logical declaration supplies 3.75rem on the left and the physical declaration supplies 1rem on the right. Both values apply. The same rule changes from a conflict to a two-sided combination based on direction.
Shorthands create quieter versions of this problem:
.result-notice {
padding-inline-end: 3.75rem;
padding: 1rem;
}The later physical shorthand sets all four physical sides and wins on whichever side padding-inline-end maps to. The special end padding disappears in both directions.
Component resets are frequent culprits. A base class may declare logical padding correctly, then a size variant or utility class applies padding: 0. DevTools shows both declarations, and the logical one can look reassuringly present while the computed side is zero. Presence is not victory. Inspect the winning declaration.
When debugging, check:
- later
paddingshorthands; padding-leftandpadding-rightfrom utilities;- component variants with higher specificity;
- declarations in later cascade layers;
- local changes to
directionorwriting-mode; !important, the small emergency that becomes local government.
Avoid maintaining physical and logical versions for the same edge unless a browser fallback requires it. One model per edge is far easier to inspect.
A fallback can add padding on both sides#
MDN marks the logical padding longhands as broadly available since January 2020 and the padding-inline shorthand since April 2021. Check those dates against your own browser policy rather than adding a fallback by reflex.
This common-looking pair is unsafe for asymmetric RTL padding:
.specimen-note {
padding-left: 1.5rem;
padding-inline-start: 1.5rem;
}In LTR, both declarations map left, so the result looks fine. In RTL, padding-left stays left while padding-inline-start maps right. The note gains 1.5rem on both sides.
If an older browser must receive a physical fallback, clear the physical sides inside a feature query before applying the logical declaration:
.specimen-note {
padding-left: 1.5rem;
}
@supports (padding-inline-start: 1rem) {
.specimen-note {
padding-left: 0;
padding-inline-start: 1.5rem;
}
}This preserves the old LTR fallback and prevents a supporting RTL browser from keeping padding on both sides. It does not give the old browser an adaptive RTL version. If legacy RTL support is required, add an explicit physical RTL override for that browser range and test it there. Otherwise, the fallback has merely moved the unsupported part into a more elaborate arrangement.
Padding can expose a box-sizing bug#
Changing padding-left to padding-inline-start does not alter the total amount of padding. It can still reveal overflow because the larger side moves to an edge where nearby positioning, transforms or container constraints differ.
The box model matters more directly when a component has an explicit size. Under the default box-sizing: content-box, declared width applies to the content box. Padding and borders are added outside it. A panel with width: 100% plus inline padding can therefore become wider than its container.
.results-panel {
width: 100%;
padding-inline: 1.5rem;
}The logical property is not causing the overflow. The panel would be too wide with equivalent left and right padding as well. The direction change may simply make the overhang easier to notice.
When the declared size should include padding and border, use border-box:
.results-panel {
box-sizing: border-box;
inline-size: 100%;
padding-inline: 1.5rem;
}MDN's box-sizing reference explains that border-box includes padding and border within the declared size, while content-box adds them to it. A project-wide border-box reset can make component sizing more predictable, but verify third-party widgets before changing their box model on principle. Principles do not answer support tickets.
If a component still widens the document, compare scrollWidth with clientWidth, inspect the overhanging element and follow the RTL horizontal-overflow guide. R040 reports document-level horizontal overflow, which is the symptom. You still need to determine whether padding, a fixed width, positioning or another declaration created it.
Values have rules of their own#
Logical padding accepts the same non-negative length and percentage values as physical padding. It does not accept auto, and negative padding is invalid.
.result-card {
padding-inline-start: auto; /* Invalid */
padding-inline-end: -1rem; /* Invalid */
}If you need one item to absorb free space, that is a margin or layout-alignment job, not padding. Padding expands the internal space of the box; it does not negotiate leftover space with siblings.
Percentages deserve care too:
.result-card {
padding-inline: 4% 7%;
}According to the MDN padding-inline-start reference, percentage padding is resolved against the inline size of the containing block. It is not a percentage of the element's own width, and it is not a percentage of the text length, although both explanations have probably appeared in code review with great confidence.
The first percentage remains start and the second remains end, so their physical sides swap in RTL. Resize the containing block when testing percentage padding. A desktop screenshot tells you almost nothing about what 7% will do inside a narrow nested panel.
Use calc() and custom properties when a component has named spacing requirements:
.result-notice {
--notice-pad: 1.25rem;
--dismiss-space: 2.5rem;
padding-inline-start: var(--notice-pad);
padding-inline-end: calc(var(--notice-pad) + var(--dismiss-space));
}The variable names describe the job rather than a side. This matters when a design token survives longer than the person who remembers why --padding-right-large-2 exists.
The element's own direction controls the mapping#
Logical padding maps from the target element's computed writing mode and direction, not from the language code and not necessarily from the document root.
An RTL clinical portal may contain an LTR machine-output panel:
<main dir="rtl" lang="ar">
<section class="machine-output" dir="ltr" lang="en">
<h2>Analyzer output</h2>
<pre>Sequence verified</pre>
</section>
</main>.machine-output {
padding-inline-start: 1.5rem;
border-inline-start: 4px solid #4f6f67;
}The section's computed direction is LTR, so inline start maps left inside it. The outer Arabic page does not force the padding to the right across an explicit direction boundary. That may be the intended result for an English machine readout.
If the component unexpectedly maps to the wrong physical side, inspect its own computed direction and writing-mode. Do not check only <html>. A local dir="ltr", a CSS direction override or an embedded widget can change the mapping. The HTML dir="rtl" guide explains how direction boundaries should be declared; this page will resist recreating the entire subject inside a padding box.
lang="ar" alone does not make an element RTL. Language and direction solve different problems. Make the document or component direction explicit, then let logical padding follow it.
Apply padding to table cells, not table rows#
Logical padding does not apply to every table display type. MDN excludes table row groups, header groups, footer groups, rows, column groups and columns. If a results table needs internal cell spacing, put it on the cells:
.lab-results th,
.lab-results td {
padding-block: 0.75rem;
padding-inline: 1rem 1.5rem;
}Do not put the padding on tr and assume the browser will distribute it among the cells. Style th and td, then inspect borders and backgrounds under the table's actual border-collapse setting. Table layout has enough special rules already. It does not need improvisation.
If the first and last columns need different edge spacing, prefer selectors that assign logical cell padding rather than hard-coded leftmost and rightmost padding. Test the same column order under the actual document direction, because grid and table rows begin on the right in an RTL page.
Test the content edge, not just the outer box#
A mirrored border can make a component look correct while its content edge remains wrong. Test where the text begins, where controls sit and whether the reserved spaces still correspond.
Render the same component under both directions:
<section dir="ltr" lang="en">
<!-- Render the result notice here. -->
</section>
<section dir="rtl" lang="ar">
<!-- Render the same result notice here. -->
</section>Inspect computed styles on the padded element:
const notice = document.querySelector(".result-notice");
const styles = getComputedStyle(notice);
console.table({
direction: styles.direction,
paddingInlineStart: styles.paddingInlineStart,
paddingInlineEnd: styles.paddingInlineEnd,
paddingLeft: styles.paddingLeft,
paddingRight: styles.paddingRight,
boxSizing: styles.boxSizing,
});For the corrected notice, LTR should compute to 1.25rem-equivalent padding on the left and 3.75rem-equivalent padding on the right. RTL should compute to the reverse physical sides. DevTools normally reports computed lengths in pixels, so compare the sides and relationship rather than expecting the authored unit to survive.
Then make the component uncomfortable on purpose:
- use the longest realistic Arabic title and body copy in your fixtures;
- reduce the viewport until the notice wraps to several lines;
- zoom the page and check that the dismiss control does not cover text;
- test with and without the dismissible variant;
- check focus outlines against the padded edges;
- render the component inside a local LTR boundary on the RTL page;
- inspect a size variant that applies
paddingshorthand; - compare the element's border-box width with its container;
- verify document overflow after the largest padding state.
R030 reports direction-sensitive physical properties without an RTL override. That can identify padding declarations that deserve review, but the review still needs the component's intent. A fixed physical chart edge should remain physical. An end-side action reserve should not.
Common mistakes#
Treating two values as left and right#
In padding-inline: 1rem 3rem, the values are start and end. They map to left and right in LTR, then right and left in RTL.
Moving the control but not its reserved space#
An absolutely positioned action and the padding that protects content from it form one layout decision. Convert both to the same logical edge and test them with long text.
Letting a later padding shorthand erase the fix#
Physical and logical properties compete in the cascade when they map to the same side. Inspect variants, resets and utility classes after the logical declaration.
Using padding to space siblings#
Use flex or grid gap for space between children. Use padding for space between a box edge and its contents. The distinction becomes visible when backgrounds, focus rings, wrapping or click targets enter the room.
Expecting logical padding to cure overflow#
It changes the side, not the total size. Check box-sizing, declared inline size, min-content behavior and positioned children. Direction can reveal an overflow bug without being its cause.
Keep the content and its edge together#
Logical padding is useful when internal space belongs to the reading flow: room before a label, room after a notice, or equal space on both inline sides. Convert the related control, border or decoration at the same time, preserve the LTR geometry, and inspect the computed physical sides in RTL.
A free Ritla scan can surface direction-sensitive physical padding through R030 and document overflow through R040. Review those findings against the component's intended edge, then add the same narrow and long-copy states to the Arabic QA checklist for frontend engineers. The goal is not to remove every left and right. It is to stop making the text remember where the English version put the furniture.
FAQ
Is padding-inline only for RTL websites?
No. It describes spacing along the inline axis in any writing direction. A single declaration can serve both LTR and RTL components, which is why it is useful for multilingual interfaces.
Is padding-inline: 16px 24px left then right?
No. It is inline start then inline end. In horizontal LTR, that maps to 16px left and 24px right. In horizontal RTL, it maps to 16px right and 24px left.
Can padding be negative or auto?
No. Logical padding accepts non-negative lengths and percentages. Use margin or alignment when you need free-space distribution, and change the layout rather than trying to subtract internal space with negative padding.
Should symmetrical left and right padding be converted?
padding-left: 1rem plus padding-right: 1rem already looks the same in both directions. padding-inline: 1rem expresses the axis more clearly and is easier to change as one value, but the conversion is not an urgent visual fix.
Why does padding-inline-start map left inside my RTL page?
Inspect the element's own computed direction. It may sit inside an explicit LTR boundary or have a local direction rule. Logical properties map from the element, not from a distant ancestor you expected to remain in charge.