left:50% With width:fit-content Makes max-width Never Apply
※本記事にはアフィリエイトリンクを含む場合があります。内容は広告の有無に影響されません。
結論
With position:absolute, width:fit-content and left:50%, shrink-to-fit sets available width to the containing block minus the left offset, so the element caps there and max-width is never evaluated.
Conclusion
When a position:absolute element has an auto width (shrink-to-fit), a set left and an auto right, the available width used for shrink-to-fit is the containing block width minus the left offset. Centre it with left:50% and available width immediately drops to half the containing block. No matter how large max-width is, the element hits that ceiling first and wraps. max-width is never evaluated.
Symptom
A video render (Remotion) had a subtitle plate inside a scene. The width was meant to follow the text, so it used width: fit-content, and it was centred with left: 50% plus transform: translateX(-50%).
<div
style={{
position: 'absolute',
left: '50%',
bottom: 80,
transform: 'translateX(-50%)',
opacity: enter,
width: 'fit-content',
maxWidth: 1600,
padding: '16px 32px',
borderRadius: radiusSm,
backgroundColor: bg,
textAlign: 'center',
}}
>
<span style={{ color, fontSize: 42, fontWeight: 500, lineHeight: 1.5, letterSpacing: '0.02em', wordBreak: 'keep-all' }}>
{lines[lineIdx]}
</span>
</div>
The containing block was a 1920px composition canvas and the intended ceiling was 1600px. Strings that comfortably fit in 1600px were still being forced onto a second line. The width was capping out before fit-content could grow, and changing maxWidth: 1600 made no difference either.
As a side effect, the extra line made the plate taller, pushing its top edge higher than intended and overlapping a layer underneath.
Cause
In the CSS 2.1 width algorithm for absolutely positioned elements, when width: auto (shrink-to-fit) is combined with a numeric left and right: auto, available width is:
available width = containing block width - left offset - margin
Here the containing block is 1920px and left: '50%' resolves against it to 960px, so:
available width = 1920 - 960 = 960px
transform: translateX(-50%) only shifts the painted result; it does not change the left value the width algorithm reads. Shrink-to-fit compares the content’s preferred width against available width and caps at available width when the content is wider. maxWidth: 1600 would be applied as a min/max constraint at a later stage, but the cap is already fixed at 960px, so the 1600 never means anything.
Lowering left below 50% widens available width, but then the element is no longer centred. The left:50% + translateX(-50%) centring idiom is simply incompatible with shrink-to-fit width.
The fix
Change what the absolute element is for: not “centre this box” but “span the containing block”, and let flexbox do the centring. The variable-width box goes inside as a normal flow item, which takes it out of the absolute width algorithm entirely.
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 80, display: 'flex', justifyContent: 'center', pointerEvents: 'none' }}>
<div
style={{
opacity: enter,
width: 'max-content',
maxWidth: 1500,
padding: '16px 32px',
borderRadius: radiusSm,
backgroundColor: bg,
textAlign: 'center',
}}
>
<span style={{ color, fontSize: 46, fontWeight: 500, lineHeight: 1.5, letterSpacing: '0.02em', wordBreak: 'keep-all' }}>
{lines[lineIdx]}
</span>
</div>
</div>
The outer div uses left: 0; right: 0 to occupy the full containing block. Width is still auto, but with both left and right set the shrink-to-fit path is not used at all — the width resolves to the full containing block. Centring is handled by justify-content: center on the inside.
The inner div has no position, so it is an ordinary flow item and the only child of the flex container. At that position no left offset participates in shrink-to-fit, and width: max-content with maxWidth: 1500 behaves as written. The max-width was also lowered from 1600 to 1500 and the font size raised from 42 to 46, but those are visual adjustments, not part of the width fix.
Preventing a repeat
The fixed code carries a comment about the width algorithm.
// The old left:'50%' + width:'fit-content' fixed shrink-to-fit available width at
// 1920-960=960px, so maxWidth:1600 never applied and every line wrapped to two.
// That was the root of the plate's top edge rising and covering the layer below.
// Replaced with a full-bleed flex wrapper + max-content so the width is real.
Centring with position: absolute and only left looks fine on screen, and content of fixed width never shows the problem. The shrunken available width only surfaces when variable-length text is combined with width: fit-content or width: max-content. Once you want a variable width inside an absolutely positioned element used for centring, the safe move is to widen the absolute element to the full containing block and centre with flex instead.
よくある質問
Q1Why does max-width have no effect with left:50% and width:fit-content?
When width is auto, left is set and right is auto, CSS derives shrink-to-fit available width by subtracting the left offset from the containing block. With left:50% only half remains, so the real ceiling sits below max-width and max-width is never reached.
Q2How do I keep it centred and still let the width vary?
Use the absolute element only as a full-bleed wrapper (left:0; right:0) and centre with flexbox. Put the variable-width box inside as a normal flow item: there no left offset enters the calculation, so width:max-content and max-width behave as written.
Q3Is this a browser bug?
No. It is exactly what the CSS 2.1 absolute positioning width algorithm specifies. When width is auto with left set and right auto, available width for shrink-to-fit is defined as containing block width minus the left offset, so every browser produces the same result.
確認した環境
- Remotion 4.0.500 / React 19.2.8
- Fixed in a commit on 2026-07-31 (present since the original implementation)
この記事の根拠
- TypeScriptファイル 392〜424行目コミット cec0dd8
- TypeScriptファイル 392〜440行目コミット 57e6a99
本文の主張は、上の記録に書かれていることだけです。運用しているリポジトリは非公開のため リンクは張れませんが、どのファイルの何行目を、どのコミット時点で見て書いたかは 記事ごとに残しています。推測で書いた箇所はありません。