[GH-ISSUE #406] Relative and absolute positions do not work properly #104

Closed
opened 2026-03-02 23:44:34 +03:00 by kerem · 7 comments
Owner

Originally created by @imaai on GitHub (Dec 11, 2025).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/406

Seems like it's a little broken.

export const Application = () => {
	return (
		<box width="100%" height="100%" backgroundColor="#000000">
			<box style={{ flexGrow: 1, backgroundColor: "red" }} />
			<box
				flexShrink={0}
				style={{ position: "relative", backgroundColor: "blue", height: 4 }}
			>
				<box
					style={{
						flexDirection: "column",
						border: ["left"],
						borderStyle: "heavy",
						borderColor: "#fb8500",
						backgroundColor: "green",
						position: "absolute",
						top: 0,
						left: 0,
						right: 0,
					}}
				>
					<text>I'm absolute green in blue top:0</text>
				</box>
			</box>
		</box>
	);
};
Image
export const Application = () => {
	return (
		<box width="100%" height="100%" backgroundColor="#000000">
			<box style={{ flexGrow: 1, backgroundColor: "red" }} />
			<box
				flexShrink={0}
				style={{ position: "relative", backgroundColor: "blue", height: 4 }}
			>
				<box
					style={{
						flexDirection: "column",
						border: ["left"],
						borderStyle: "heavy",
						borderColor: "#fb8500",
						backgroundColor: "green",
						position: "absolute",
						bottom: 0,
						left: 0,
						right: 0,
					}}
				>
					<text>I'm absolute green in blue bottom:0</text>
				</box>
			</box>
		</box>
	);
};
Image
Originally created by @imaai on GitHub (Dec 11, 2025). Original GitHub issue: https://github.com/anomalyco/opentui/issues/406 Seems like it's a little broken. ``` export const Application = () => { return ( <box width="100%" height="100%" backgroundColor="#000000"> <box style={{ flexGrow: 1, backgroundColor: "red" }} /> <box flexShrink={0} style={{ position: "relative", backgroundColor: "blue", height: 4 }} > <box style={{ flexDirection: "column", border: ["left"], borderStyle: "heavy", borderColor: "#fb8500", backgroundColor: "green", position: "absolute", top: 0, left: 0, right: 0, }} > <text>I'm absolute green in blue top:0</text> </box> </box> </box> ); }; ``` <img width="1980" height="1516" alt="Image" src="https://github.com/user-attachments/assets/f29562bc-47ff-474e-97d3-628c9adcee2e" /> --- ``` export const Application = () => { return ( <box width="100%" height="100%" backgroundColor="#000000"> <box style={{ flexGrow: 1, backgroundColor: "red" }} /> <box flexShrink={0} style={{ position: "relative", backgroundColor: "blue", height: 4 }} > <box style={{ flexDirection: "column", border: ["left"], borderStyle: "heavy", borderColor: "#fb8500", backgroundColor: "green", position: "absolute", bottom: 0, left: 0, right: 0, }} > <text>I'm absolute green in blue bottom:0</text> </box> </box> </box> ); }; ``` <img width="1980" height="1516" alt="Image" src="https://github.com/user-attachments/assets/3c64ff9d-513f-4878-8ce1-849cb3cc7094" />
kerem 2026-03-02 23:44:34 +03:00
Author
Owner

@kommander commented on GitHub (Dec 11, 2025):

Contrary to web browsers absolute elements are always global absolute, not relative to their parents. That is currently expected behaviour. I am open to changing that and make absolutely positioned elements relative to the closest explicitly relative positioned parent, then it would be browser like behavior.

<!-- gh-comment-id:3641559525 --> @kommander commented on GitHub (Dec 11, 2025): Contrary to web browsers absolute elements are always global absolute, not relative to their parents. That is currently expected behaviour. I am open to changing that and make absolutely positioned elements relative to the closest explicitly relative positioned parent, then it would be browser like behavior.
Author
Owner

@imaai commented on GitHub (Dec 11, 2025):

@kommander

are always global absolute, not relative to their parents

When running first example I thought it behaves like "fixed" position, but notice that in second example I set bottom: 0, green container is being positioned in the fourth line from the top. Just like the height of the relative container.

I checked that it computes the position based on the height of closest parent height, which i find super confusing.

<box width="100%" height="100%" backgroundColor="#000000">
	<box style={{ flexGrow: 1, backgroundColor: "red" }} />
	<box flexShrink={0} style={{ backgroundColor: "blue", height: 4 }}>
		<box
			style={{
				flexDirection: "column",
				border: ["left"],
				borderStyle: "heavy",
				borderColor: "#fb8500",
				backgroundColor: "green",
				position: "absolute",
				bottom: 0,
				left: 0,
				right: 0,
			}}
		>
			<text>I'm absolute green in blue bottom:0</text>
		</box>
	</box>
</box>
<!-- gh-comment-id:3642008388 --> @imaai commented on GitHub (Dec 11, 2025): @kommander > are always global absolute, not relative to their parents When running **first** example I thought it behaves like "fixed" position, but notice that in **second** example I set `bottom: 0`, green container is being positioned in the fourth line from the top. Just like the height of the relative container. I checked that it computes the position based on the height of closest parent height, which i find super confusing. ``` <box width="100%" height="100%" backgroundColor="#000000"> <box style={{ flexGrow: 1, backgroundColor: "red" }} /> <box flexShrink={0} style={{ backgroundColor: "blue", height: 4 }}> <box style={{ flexDirection: "column", border: ["left"], borderStyle: "heavy", borderColor: "#fb8500", backgroundColor: "green", position: "absolute", bottom: 0, left: 0, right: 0, }} > <text>I'm absolute green in blue bottom:0</text> </box> </box> </box> ```
Author
Owner

@kommander commented on GitHub (Dec 11, 2025):

That is either a yoga quirk, or the yoga nodes are not set up properly. Does it work with the yoga web examples https://www.yogalayout.dev/playground ?

<!-- gh-comment-id:3642555942 --> @kommander commented on GitHub (Dec 11, 2025): That is either a yoga quirk, or the yoga nodes are not set up properly. Does it work with the yoga web examples https://www.yogalayout.dev/playground ?
Author
Owner

@imaai commented on GitHub (Dec 11, 2025):

@kommander In yoga playground it works as expected. Also for the relatives.

<Layout config={{useWebDefaults: false}}>
  <Node style={{width: 350, height: 350, padding: 20}}>
    <Node style={{width: "100%", height: "100%"}}>
      <Node style={{flex: 1 }} />
      <Node style={{flexShrink: 0, height: 50 }}>
        <Node style={{position: "absolute", bottom: 20, height: 20, width: 20 }} />
      </Node>
    </Node>
  </Node>
</Layout>
Image
<!-- gh-comment-id:3642715866 --> @imaai commented on GitHub (Dec 11, 2025): @kommander In yoga playground it works as expected. Also for the relatives. ``` <Layout config={{useWebDefaults: false}}> <Node style={{width: 350, height: 350, padding: 20}}> <Node style={{width: "100%", height: "100%"}}> <Node style={{flex: 1 }} /> <Node style={{flexShrink: 0, height: 50 }}> <Node style={{position: "absolute", bottom: 20, height: 20, width: 20 }} /> </Node> </Node> </Node> </Layout> ``` <img width="974" height="1124" alt="Image" src="https://github.com/user-attachments/assets/058ac4fc-17fe-4281-af73-4ce83b2546fa" />
Author
Owner

@kommander commented on GitHub (Dec 11, 2025):

Then we need to check how opentui sets up the nodes differently.

<!-- gh-comment-id:3642735402 --> @kommander commented on GitHub (Dec 11, 2025): Then we need to check how opentui sets up the nodes differently.
Author
Owner

@edlsh commented on GitHub (Dec 13, 2025):

Looked into this. Pretty sure the issue is in the x/y getters in Renderable.ts.

Yoga's getComputedLayout() always returns positions relative to the parent (confirmed here), but we only add the parent offset for relative elements and skip it for absolute. That's backwards.

So when you set bottom: 0, Yoga does the right thing and computes top = parentHeight - childHeight, but then we don't add where the parent actually is on screen. That's why you get the weird behavior.

Fix is just removing the position type check:

public get x(): number {
  if (this.parent) {
    return this.parent.x + this._x + this._translateX
  }
  return this._x + this._translateX
}

This matches what Yoga's docs describe for containing blocks.

<!-- gh-comment-id:3649317913 --> @edlsh commented on GitHub (Dec 13, 2025): Looked into this. Pretty sure the issue is in the `x`/`y` getters in `Renderable.ts`. Yoga's `getComputedLayout()` always returns positions relative to the parent ([confirmed here](https://github.com/facebook/yoga/issues/787)), but we only add the parent offset for `relative` elements and skip it for `absolute`. That's backwards. So when you set `bottom: 0`, Yoga does the right thing and computes `top = parentHeight - childHeight`, but then we don't add where the parent actually is on screen. That's why you get the weird behavior. Fix is just removing the position type check: ```typescript public get x(): number { if (this.parent) { return this.parent.x + this._x + this._translateX } return this._x + this._translateX } ``` This matches what [Yoga's docs](https://yogalayout.dev/docs/advanced/containing-block) describe for containing blocks.
Author
Owner

@kommander commented on GitHub (Dec 13, 2025):

I am afraid it's not as simple as that, because x/y are considered screen coordinates that the renderables are rendered to. If you don't add the parent offset, all Renderables start at the top right 0/0 position of the screen.

Edit: re your PR, what is absolute positioning then when parent offset is always added?

<!-- gh-comment-id:3649502906 --> @kommander commented on GitHub (Dec 13, 2025): I am afraid it's not as simple as that, because x/y are considered screen coordinates that the renderables are rendered to. If you don't add the parent offset, all Renderables start at the top right 0/0 position of the screen. Edit: re your PR, what is absolute positioning then when parent offset is always added?
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#104
No description provided.