Skip to content

layout: Fix baseline alignment of horizontal text in column flex container - #46697

Merged
simonwuelker merged 3 commits into
mainfrom
flex-horizontal-baseline-alignment-in-vert-container
Jul 28, 2026
Merged

simonwuelker merged 3 commits into
mainfrom
flex-horizontal-baseline-alignment-in-vert-container

Conversation

@simonwuelker

@simonwuelker simonwuelker commented Jul 21, 2026

Copy link
Copy Markdown
Member

When flex items have no natural baseline (such as text items in a container with flex-direction: column) then we must synthesize one from the items border box. In that case the baseline is given by the line-under border edge. So far we've been assuming that the line-under edge is always the cross-end edge of the flex item. But in reality, it depends on the flex axis and the writing mode of the item and its container. In particular, the current code incorrectly aligns items by their right border edge for writing-mode: initial and flex-direction: column (the baseline should be the left border edge).

This change instead follows the algorithm from the specification for computing a synthetic baseline.

Testing: New tests start to pass. css/css-flexbox/align-items-baseline-column-vert-lr-items.htmlregresses - this test uses baselines from vertical text, but we don't support vertical writing modes yet. I've also added a new test as the interaction between flex-direction: row-reverse and direction: rtl was not covered by WPT.

@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Jul 21, 2026
@simonwuelker

simonwuelker commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

A test case that is improved by this change is

<style>
    * {
        outline: 1px solid black;
    }

    div {
        display: flex;
        flex-direction: column;
        align-items: baseline;
    }
</style>

<body>
    <div>
        <h1>This text</h1>
        <p>should be left aligned.</p>
    </div>
</body>

@simonwuelker simonwuelker changed the title layout: Fix baseline alignment of text in column flex container layout: Fix baseline alignment of horizontal text in column flex container Jul 21, 2026
@simonwuelker
simonwuelker force-pushed the flex-horizontal-baseline-alignment-in-vert-container branch from 3e24119 to c203622 Compare July 21, 2026 18:22
@mrobinson

mrobinson commented Jul 23, 2026

Copy link
Copy Markdown
Member

Please take another look at the failing tests as dir: rtl should have full support. We do not support vertical writing modes yet though.

@simonwuelker
simonwuelker marked this pull request as draft July 23, 2026 10:01
@simonwuelker
simonwuelker force-pushed the flex-horizontal-baseline-alignment-in-vert-container branch 2 times, most recently from 6696195 to ffbc462 Compare July 23, 2026 18:42
@simonwuelker simonwuelker added the T-linux-wpt Do a try run of the WPT label Jul 23, 2026
@github-actions github-actions Bot removed the T-linux-wpt Do a try run of the WPT label Jul 23, 2026
@github-actions

Copy link
Copy Markdown

🔨 Triggering try run (#30034882777) for Linux (WPT)

@simonwuelker
simonwuelker force-pushed the flex-horizontal-baseline-alignment-in-vert-container branch from ffbc462 to 141f7a7 Compare July 23, 2026 18:52
@github-actions

Copy link
Copy Markdown

Test results for linux-wpt from try job (#30034882777):

Flaky unexpected result (33)
  • OK /IndexedDB/idbdatabase_deleteObjectStore.any.sharedworker.html
    • PASS [expected FAIL] subtest: Deleted object store's name should be removed from database's list. Attempting to use a deleted IDBObjectStore should throw an InvalidStateError
  • TIMEOUT [expected OK] /IndexedDB/idbfactory-deleteDatabase-request-success.any.html
    • TIMEOUT [expected PASS] subtest: IDBFactory deleteDatabase() request properties on success

      Test timed out
      

  • ERROR [expected OK] /_mozilla/mozilla/window.html
  • TIMEOUT [expected OK] /_webgl/conformance/textures/misc/tex-image-and-sub-image-2d-with-array-buffer-view.html
    • NOTRUN [expected PASS] subtest: Overall test
  • CRASH [expected OK] /_webgl/conformance/uniforms/no-over-optimization-on-uniform-array-06.html
  • TIMEOUT [expected OK] /_webgl/conformance2/attribs/gl-bindAttribLocation-aliasing-inactive.html
    • NOTRUN [expected PASS] subtest: Overall test
  • TIMEOUT [expected OK] /_webgl/conformance2/sync/sync-webgl-specific.html
  • TIMEOUT [expected OK] /cookies/attributes/domain.sub.html
    • TIMEOUT [expected PASS] subtest: No host cookies returned for host cookies after non-host redirect 1

      Test timed out
      

    • NOTRUN [expected PASS] subtest: No host cookies returned for host cookies after non-host redirect 2
    • NOTRUN [expected PASS] subtest: No host cookies returned for host cookies after non-host redirect 3
  • OK /css/CSS2/linebox/inline-negative-margin-001.html (#43763)
    • FAIL [expected PASS] subtest: [data-expected-height] 3

      assert_equals: 
      &lt;div class="w4" data-expected-height="10"&gt;123 &lt;span style="margin-left: -4ch"&gt;1234 &lt;/span&gt;&lt;/div&gt;
      height expected 10 but got 20
      

    • FAIL [expected PASS] subtest: [data-expected-height] 4

      assert_equals: 
      &lt;div class="w4" data-expected-height="10"&gt;123 &lt;span style="margin-left: -4ch"&gt;123 &lt;/span&gt;&lt;/div&gt;
      height expected 10 but got 20
      

    • FAIL [expected PASS] subtest: [data-expected-height] 5

      assert_equals: 
      &lt;div class="w4" data-expected-height="10"&gt;&lt;img class="w4"&gt;&lt;span style="margin-left: -4ch"&gt;123&lt;/span&gt;&lt;/div&gt;
      height expected 10 but got 20
      

    • FAIL [expected PASS] subtest: [data-expected-height] 8

      assert_equals: 
      &lt;div class="w4" data-expected-height="10"&gt;&lt;img class="w4"&gt;&lt;img class="w1" style="margin-left: -1ch"&gt;&lt;/div&gt;
      height expected 10 but got 20
      

  • TIMEOUT [expected OK] /css/compositing/canvas-composite-modes.html
  • FAIL [expected PASS] /css/css-backgrounds/background-size-042.html
  • FAIL [expected PASS] /css/css-fonts/font-feature-settings-descriptor-01.html (#45418)
  • OK /css/css-fonts/variations/at-font-face-font-matching.html (#20684)
    • FAIL [expected PASS] subtest: Matching font-style: 'oblique 21deg' should prefer 'oblique 30deg 60deg' over 'oblique 40deg 50deg'

      assert_equals: Unexpected font on test element expected 487 but got 532
      

    • FAIL [expected PASS] subtest: Matching font-style: 'oblique 0deg' should prefer 'oblique 5deg' over 'oblique 15deg 20deg'

      assert_equals: Unexpected font on test element expected 487 but got 532
      

    • FAIL [expected PASS] subtest: Matching font-style: 'oblique -21deg' should prefer 'oblique 0deg' over 'oblique 30deg 60deg'

      assert_equals: Unexpected font on test element expected 487 but got 532
      

  • FAIL [expected PASS] /css/css-sizing/dynamic-available-size-iframe.html (#45763)
  • OK /dom/nodes/moveBefore/iframe-document-preserve.window.html (#43152)
    • FAIL [expected PASS] subtest: moveBefore(): cross-origin iframe is preserved: remove new parent

      assert_equals: iframe does not fire a second load event expected 1 but got 0
      

  • TIMEOUT [expected OK] /fetch/content-encoding/br/big-br-body.https.any.worker.html
    • TIMEOUT [expected PASS] subtest: large br data should be decompressed successfully

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: large br data should be decompressed successfully with byte stream
  • TIMEOUT [expected OK] /fetch/content-encoding/gzip/big-gzip-body.https.any.sharedworker.html
    • TIMEOUT [expected PASS] subtest: large gzip data should be decompressed successfully

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: large gzip data should be decompressed successfully with byte stream
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/accumulated-oversized-payload.https.window.html (#41705)
  • ERROR [expected OK] /fetch/metadata/window-open.https.sub.html (#40339)
  • OK /html/browsers/browsing-the-web/navigating-across-documents/005.html (#27062)
    • PASS [expected FAIL] subtest: Link with onclick navigation and href navigation
  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/supported-elements.html (#24145)
    • TIMEOUT [expected FAIL] subtest: Element with tabindex should support autofocus

      Test timed out
      

    • NOTRUN [expected PASS] subtest: Non-HTMLElement should not support autofocus
    • NOTRUN [expected FAIL] subtest: Host element with delegatesFocus should support autofocus
    • NOTRUN [expected FAIL] subtest: Host element with delegatesFocus including no focusable descendants should be skipped
    • NOTRUN [expected FAIL] subtest: Area element should support autofocus
  • TIMEOUT [expected OK] /html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_other_frame_popup.sub.html (#39702)
    • TIMEOUT [expected FAIL] subtest: Sandboxed iframe can not navigate other frame's popup

      Test timed out
      

  • TIMEOUT [expected OK] /html/semantics/forms/form-submission-0/form-submit-iframe-then-location-navigate.html (#29634)
    • TIMEOUT [expected FAIL] subtest: Verifies that location navigations take precedence when following form submissions.

      Test timed out
      

  • OK /mixed-content/tentative/autoupgrades/audio-upgrade.https.sub.html (#41697)
    • FAIL [expected PASS] subtest: Audio autoupgraded

      assert_equals: Length expected 1 but got Infinity
      

  • OK /mixed-content/tentative/autoupgrades/video-upgrade.https.sub.html (#41135)
    • FAIL [expected PASS] subtest: Video autoupgraded

      assert_equals: Length. expected 1 but got Infinity
      

  • OK /paint-timing/fcp-only/fcp-text-input.html
    • PASS [expected FAIL] subtest: Text input should become contentful when its value is non-empty
  • TIMEOUT [expected OK] /pointerevents/compat/pointerevent_touch-action_two-finger_interaction.html (#44693)
    • NOTRUN [expected PASS] subtest: touch two-finger pan on 'touch-action: pan-x pan-y'
    • NOTRUN [expected FAIL] subtest: touch two-finger pan on 'touch-action: pinch-zoom'
  • OK /pointerevents/pointerevent_pointercapture_in_frame.html?pen (#45536)
    • FAIL [expected PASS] subtest: Test penpointer capture in same-origin frame: Pointer down at inner frame and set pointer capture.

      assert_true: Timed out waiting for lostpointercapture expected true got false
      

    • FAIL [expected PASS] subtest: Test penpointer capture in same-origin frame: Pointer down at inner frame and set pointer capture to outer frame should not capture.

      assert_array_equals: Received events: innerFrame received pointerdown,innerFrame received pointermove,innerFrame received pointerup,innerFrame received lostpointercapture lengths differ, expected array ["innerFrame received pointerdown", "innerFrame received pointermove", "innerFrame received pointerup"] length 3, got ["innerFrame received pointerdown", "innerFrame received pointermove", "innerFrame received pointerup", "innerFrame received lostpointercapture"] length 4
      

  • OK /resize-observer/observer-in-cross-origin-frame.sub.html
    • FAIL [expected PASS] subtest: Cross-origin observer responds to explicitly set physical size

      assert_equals: expected 200 but got 284
      

  • CRASH [expected OK] /trusted-types/script-enforcement-016.html
  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?01-05 (#38975)
    • PASS [expected TIMEOUT] subtest: Navigate a window via anchor with javascript:-urls in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a window via anchor with javascript:-urls w/ default policy in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a frame via anchor with javascript:-urls in enforcing mode.
  • TIMEOUT [expected OK] /trusted-types/trusted-types-navigation.html?16-20 (#44835)
    • FAIL [expected PASS] subtest: Navigate a frame via area with javascript:-urls in report-only mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

    • TIMEOUT [expected PASS] subtest: Navigate a frame via area with javascript:-urls w/ default policy in report-only mode.

      Test timed out
      

  • CRASH [expected OK] /webstorage/storage_local_setitem_quotaexceedederr.window.html
Stable unexpected results that are known to be intermittent (19)
  • TIMEOUT /FileAPI/url/url-in-tags-revoke.window.html (#19978)
    • TIMEOUT [expected PASS] subtest: Fetching a blob URL immediately before revoking it works in &lt;script&gt; tags.

      Test timed out
      

  • OK /_webgl/conformance/textures/misc/texture-upload-size.html (#21770)
    • FAIL [expected PASS] subtest: WebGL test #85

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #87

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #89

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #91

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

  • OK /beacon/beacon-basic.https.window.html (#41723)
    • FAIL [expected PASS] subtest: Payload size restriction should be accumulated: type = string

      assert_false: expected false got true
      

    • PASS [expected FAIL] subtest: Payload size restriction should be accumulated: type = blob
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/multiple-iframes.https.window.html (#35176)
  • OK /html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-window-open.html (#28691)
    • PASS [expected FAIL] subtest: load event does not fire on window.open('about:blank')
  • OK /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html (#28697)
    • FAIL [expected PASS] subtest: aElement.click() before the load event must NOT replace

      assert_equals: expected "http://web-platform.test:8000/common/blank.html?thereplacement" but got "http://web-platform.test:8000/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/code-injector.html?pipe=sub(none)&amp;code=%0A%20%20%20%20const%20a%20%3D%20document.createElement(%22a%22)%3B%0A%20%20%20%20a.href%20%3D%20%22%2Fcommon%2Fblank.html%3Fthereplacement%22%3B%0A%20%20%20%20document.currentScript.before(a)%3B%0A%20%20%20%20a.click()%3B%0A%20%20"
      

  • TIMEOUT [expected OK] /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit.html (#44098)
    • TIMEOUT [expected FAIL] subtest: Replace before load, triggered by formElement.requestSubmit()

      Test timed out
      

  • TIMEOUT [expected ERROR] /html/browsers/history/the-location-interface/location_replace_session_history.html (#41896)
  • OK [expected TIMEOUT] /html/syntax/parsing/html5lib_write.html?file=processing-instructions (#46205)
  • OK /html/webappapis/user-prompts/print-during-unload.html (#35944)
    • FAIL [expected PASS] subtest: print() during unload

      assert_array_equals: expected property 1 to be "destination" but got "error: window.print is not a function" (expected array ["start", "destination"] got ["start", "error: window.print is not a function"])
      

  • TIMEOUT [expected OK] /infrastructure/testdriver/click_nested.html (#43887)
    • NOTRUN [expected FAIL] subtest: TestDriver click method with multiple windows and nested iframe
  • OK /paint-timing/with-lcp/fcp-document-opacity-image.html (#43068)
    • PASS [expected FAIL] subtest: Test that FCP after opacity change is not a larger value than LCP
  • OK /pointerevents/pointerevent_pointercapture_in_frame.html?touch (#45445)
    • PASS [expected FAIL] subtest: Test touchpointer capture in same-origin frame: Pointer down at inner frame and set pointer capture.
    • PASS [expected FAIL] subtest: Test touchpointer capture in same-origin frame: Pointer down at inner frame and set pointer capture to outer frame should not capture.
  • OK /resource-timing/buffer-full-then-increased.html (#44408)
    • PASS [expected FAIL] subtest: Test that overflowing the buffer and immediately increasing its limit does not trigger the resourcetimingbufferfull event
  • OK [expected CRASH] /resource-timing/render-blocking-status-link.html (#41664)
  • OK /resource-timing/test_resource_timing.https.html (#25216)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (iframe)
    • FAIL [expected PASS] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (xmlhttprequest)

      assert_equals: expected 55.72999999999999 but got 55.72
      

  • OK /webaudio/the-audio-api/the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html (#22849)
    • PASS [expected FAIL] subtest: buffer-stitching-1
  • TIMEOUT [expected OK] /webstorage/localstorage-about-blank-3P-iframe-opens-3P-window.partitioned.html (#29053)
    • TIMEOUT [expected FAIL] subtest: StorageKey: test 3P about:blank window opened from a 3P iframe

      Test timed out
      

  • OK [expected ERROR] /webxr/render_state_update.https.html (#27535)

@github-actions

Copy link
Copy Markdown

✨ Try run (#30034882777) succeeded.

@simonwuelker
simonwuelker force-pushed the flex-horizontal-baseline-alignment-in-vert-container branch from 141f7a7 to d9aac0c Compare July 23, 2026 19:53
@simonwuelker
simonwuelker marked this pull request as ready for review July 23, 2026 19:56
@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

🤖 Opened new upstream WPT pull request (web-platform-tests/wpt#61505) with upstreamable changes.

@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

✍ Updated existing upstream WPT pull request (web-platform-tests/wpt#61505) title and body.

1 similar comment
@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

✍ Updated existing upstream WPT pull request (web-platform-tests/wpt#61505) title and body.

@simonwuelker
simonwuelker force-pushed the flex-horizontal-baseline-alignment-in-vert-container branch from d9aac0c to c510046 Compare July 23, 2026 22:08
@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

📝 Transplanted new upstreamable changes to existing upstream WPT pull request (web-platform-tests/wpt#61505).

@simonwuelker
simonwuelker force-pushed the flex-horizontal-baseline-alignment-in-vert-container branch from c510046 to 8d6cc58 Compare July 27, 2026 19:01
@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

📝 Transplanted new upstreamable changes to existing upstream WPT pull request (web-platform-tests/wpt#61505).

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Jul 28, 2026
@simonwuelker
simonwuelker added this pull request to the merge queue Jul 28, 2026
@servo-highfive servo-highfive added the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Jul 28, 2026
Merged via the queue into main with commit d33cbb7 Jul 28, 2026
33 checks passed
@simonwuelker
simonwuelker deleted the flex-horizontal-baseline-alignment-in-vert-container branch July 28, 2026 10:09
@servo-highfive servo-highfive removed the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants