From ac02d06cfbdf7b5ddfb10636e2acdfd2e3c12090 Mon Sep 17 00:00:00 2001 From: nearnshaw Date: Wed, 1 Jul 2026 17:16:27 -0300 Subject: [PATCH 1/3] text-border --- creator/sdk7/2d-ui/ui_text.md | 4 ++++ creator/sdk7/3d-essentials/text.md | 13 +++++++++++++ .../sdk7/building-for-mobile/ui-best-practices.md | 1 + creator/sdk7/design-experience/ux-ui-guide.md | 4 ++++ 4 files changed, 22 insertions(+) diff --git a/creator/sdk7/2d-ui/ui_text.md b/creator/sdk7/2d-ui/ui_text.md index f0042ef0..5159fc06 100644 --- a/creator/sdk7/2d-ui/ui_text.md +++ b/creator/sdk7/2d-ui/ui_text.md @@ -26,6 +26,10 @@ A `Label` entity has the following fields that can be configured: A `Label` entity can also have other common components found on other types of UI entities, like `uiTransform` and `uiBackground`. +{% hint style="info" %} +**πŸ’‘ Tip**: To keep text legible, give it plenty of contrast against whatever is behind it. UI text has no outline property, so the recommended approach is to place the text over a solid `uiBackground` in a contrasting color. This matters especially for players on mobile, who may be viewing the screen under varying lighting conditions. For in-world text via [`TextShape`](../3d-essentials/text.md#text-shadow-and-outline-properties), use a thin outline instead. +{% endhint %} + _**ui.tsx file:**_ ```ts diff --git a/creator/sdk7/3d-essentials/text.md b/creator/sdk7/3d-essentials/text.md index 73d191c3..f0d9da00 100644 --- a/creator/sdk7/3d-essentials/text.md +++ b/creator/sdk7/3d-essentials/text.md @@ -150,6 +150,19 @@ The letters in the text can also have an outline in a different color surroundin * `outlineWidth`: _number_. How wide the text outline will be, in all directions, as a number from 0 to 1. By default _0_, which makes it invisible. * `outlineColor`: _Color3_ object. _Color3_ objects store an _RBG_ color as three numbers from 0 to 1. +{% hint style="info" %} +**πŸ’‘ Tip**: It's a good practice to give your text a thin outline in a contrasting color. This keeps the text legible against whatever is behind it, as the background may vary with the scene's lighting conditions. This is especially important for players viewing the scene on mobile. + +```ts +TextShape.create(sign, { + text: 'Hello World', + textColor: { r: 1, g: 1, b: 1, a: 1 }, + outlineWidth: 0.1, + outlineColor: { r: 0, g: 0, b: 0 }, +}) +``` +{% endhint %} + ## Multiple lines If you want your text to span multiple lines, use `\n` as part of the string. The following example has two separate lines of text: diff --git a/creator/sdk7/building-for-mobile/ui-best-practices.md b/creator/sdk7/building-for-mobile/ui-best-practices.md index 8435be88..44acd7aa 100644 --- a/creator/sdk7/building-for-mobile/ui-best-practices.md +++ b/creator/sdk7/building-for-mobile/ui-best-practices.md @@ -13,6 +13,7 @@ There is no single proven recipe for Decentraland mobile UI yet β€” the platform * **Minimize options.** Show only what the player needs right now and progressively disclose the rest. * **Place actionable dialogs at the center of the screen** β€” anywhere a player needs to read and respond. * **Place non-actionable messages at the top-center** β€” status, notifications, and ambient information. +* **Give text strong contrast against its background.** Mobile players often view their screens under suboptimal lighting, which makes reading harder. For 2D UI text, place it over a solid, contrasting [`uiBackground`](../2d-ui/ui_background.md). For in-world text via [`TextShape`](../3d-essentials/text.md#text-shadow-and-outline-properties), give it a thin outline in a contrasting color. ## DON'Ts diff --git a/creator/sdk7/design-experience/ux-ui-guide.md b/creator/sdk7/design-experience/ux-ui-guide.md index 2500b322..ddf45178 100644 --- a/creator/sdk7/design-experience/ux-ui-guide.md +++ b/creator/sdk7/design-experience/ux-ui-guide.md @@ -126,6 +126,10 @@ If you are going to place text that is displayed over imagery (or over the world
Text over images

Prioritize legibility

+{% hint style="info" %} +**πŸ’‘ Tip**: Backgrounds may vary with the scene's lighting conditions, and players on mobile often view their screens under suboptimal light. To keep text readable in every situation, always give it strong contrast against its background. For 2D UI text, place it over a solid, contrasting [`uiBackground`](../2d-ui/ui_background.md). For in-world text via [`TextShape`](../3d-essentials/text.md#text-shadow-and-outline-properties), give it a thin outline in a contrasting color. +{% endhint %} + ### Icons Icons synthesise information, helping you identify actions through images. They are an amazingly powerful tool for providing input that can be interpreted fast, as opposed to using text to label things, which demands more of the player’s attention and time. It’s also useful to show icons in combination with text, as this helps disambiguate their meanings. From 15726bfd51eba4929f608b92bf28b8d8bd5b7eb0 Mon Sep 17 00:00:00 2001 From: nearnshaw Date: Thu, 2 Jul 2026 18:41:00 -0300 Subject: [PATCH 2/3] interactive safe UI area --- creator/sdk7/2d-ui/onscreen-ui.md | 2 +- creator/sdk7/2d-ui/ui-positioning.md | 35 ++++++++++++++++++- creator/sdk7/building-for-mobile/safe-area.md | 31 +++++++++++++++- .../building-for-mobile/ui-best-practices.md | 2 +- 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/creator/sdk7/2d-ui/onscreen-ui.md b/creator/sdk7/2d-ui/onscreen-ui.md index bc74a07f..fa12b3ee 100644 --- a/creator/sdk7/2d-ui/onscreen-ui.md +++ b/creator/sdk7/2d-ui/onscreen-ui.md @@ -18,7 +18,7 @@ Build a UI by defining a structure of nested `UIEntity` objects in JSX. The synt A simple UI with static elements can look a lot like HTML, but when you add dynamic elements that respond to a change in state, you can do things that are a lot more powerful. -The default Decentraland explorer UI includes a chat widget, a map, and other elements. These UI elements are always displayed on the top layer, above any scene-specific UI. So if your scene has UI elements that occupy the same screen space as these, they will be occluded. +The default Decentraland explorer UI includes a chat widget, a map, and other elements. These UI elements are always displayed on the top layer, above any scene-specific UI. So if your scene has UI elements that occupy the same screen space as these, they will be occluded. To keep your UI clear of these elements automatically, wrap it in the [`InteractableArea` component](ui-positioning.md#explorer-ui-insets-interactablearea). See [UX guidelines](../design-experience/ux-ui-guide.md) for tips on how to design the look and feel of your UI. diff --git a/creator/sdk7/2d-ui/ui-positioning.md b/creator/sdk7/2d-ui/ui-positioning.md index 74a8a8dc..ca1d6929 100644 --- a/creator/sdk7/2d-ui/ui-positioning.md +++ b/creator/sdk7/2d-ui/ui-positioning.md @@ -288,7 +288,7 @@ The `UiCanvasInformation` component holds the following information: * `height`: Canvas height in pixels * `width`: Canvas width in pixels * `devicePixelRatio`: The ratio of the resolution in physical pixels in the device to the pixels on the canvas -* `interactableArea`: A `BorderRect` object, detailing the area designated for scene UI elements. This object contains values for `top`, `bottom`, `left` and `right`, each of these is the number of pixels on that margin of the screen that are taken up by the explorer UI. +* `interactableArea`: A `BorderRect` object, detailing the area designated for scene UI elements. This object contains values for `top`, `bottom`, `left` and `right`, each of these is the number of pixels on that margin of the screen that are taken up by the explorer UI. Instead of reading these values manually, you can wrap your UI in the [`InteractableArea` component](#explorer-ui-insets-interactablearea) to apply them automatically. {% hint style="warning" %} **πŸ“” Note** : Different Decentraland explorers will have different values for these, as the global UIs of the platform may differ, and the values might change dynamically as the user expands or hides different global UI menus. @@ -369,3 +369,36 @@ Some other best practices regarding UI sizes: * If the width or height of any UI element is dynamic, it's good to also use the `maxWidth`, `minWidth`, `maxHeight`, and `minHeight` parameters to make sure they stay within reasonable values. * The font size of text is relative to a fixed number of pixels, you should make it dynamic so it remains readable on retina displays. See [Responsive text size](ui_text.md#responsive-text-size) + +## Explorer UI insets (`InteractableArea`) + +The Decentraland explorer draws its own UI on top of every scene: the minimap, the chat window, the profile widget, and other overlays. These elements always render above scene UI, so anything you place under them is partly hidden and can't be clicked. How much of the screen they take up depends on the platform, and can change at any moment β€” for example when the player opens or closes the chat. The portion of the screen that's free of explorer UI is called the **interactable area**. + +The `InteractableArea` component keeps your UI inside this region automatically, reading the current values that the explorer reports. Import it from `@dcl/sdk/react-ecs` and wrap the UI you want to protect: + +```tsx +import ReactEcs, { ReactEcsRenderer, UiEntity, InteractableArea } from '@dcl/sdk/react-ecs' +import { Color4 } from '@dcl/sdk/math' + +export function setupUi() { + ReactEcsRenderer.setUiRenderer(() => ( + + {/* A child sized 100% Γ— 100% fills the interactable area exactly */} + + + )) +} +``` + +`InteractableArea` positions itself absolutely using the inset values from the [`UiCanvasInformation` component](#responsive-ui-size), so the `positionType` and `position` fields of its `uiTransform` are reserved β€” any value you set for them is ignored. All other `uiTransform` properties (`padding`, `flexDirection`, `alignItems`, …) and UI components (`uiBackground`, `onMouseDown`, …) work as usual. The component reacts automatically whenever the explorer changes the reported area, for example when the player expands or hides a system menu. + +{% hint style="info" %} +**πŸ“” Note**: For now, only the **mobile client** (Godot) reports an interactable area, so the component only prevents overlaps there. Other clients will adopt it soon. Different explorers reserve different regions; when a client doesn't report an interactable area, the insets are `(0, 0, 0, 0)` and the component simply covers the whole screen, so it's always safe to leave in cross-platform UI code. +{% endhint %} + +`InteractableArea` works just like the [`ScreenInsetArea` component](../building-for-mobile/safe-area.md#device-hardware-insets-screeninsetarea), but the two protect against different things: `InteractableArea` avoids the **explorer's own UI** (minimap, chat, overlays), while `ScreenInsetArea` avoids the **device's hardware-reserved margins** on mobile (notch, status bar, home indicator). + +If you prefer to lay things out yourself, you can also read the raw inset values from `UiCanvasInformation.interactableArea`, described in [Responsive UI size](#responsive-ui-size). diff --git a/creator/sdk7/building-for-mobile/safe-area.md b/creator/sdk7/building-for-mobile/safe-area.md index 6e82563e..7e5eee6e 100644 --- a/creator/sdk7/building-for-mobile/safe-area.md +++ b/creator/sdk7/building-for-mobile/safe-area.md @@ -6,7 +6,7 @@ description: Where scene UI can safely live on mobile β€” clear of the system HU On a phone, two things eat into the screen space your UI can safely use: -* **The Decentraland system HUD** β€” the app overlays its own controls (joystick, chat, profile, camera controls, the interaction button). Scene UI placed under them clashes visually and competes for taps. These regions are mapped out in [Reserved margins](#reserved-margins) below. +* **The Decentraland system HUD** β€” the app overlays its own controls (joystick, chat, profile, camera controls, the interaction button). Scene UI placed under them clashes visually and competes for taps. These regions are mapped out in [Reserved margins](#reserved-margins) below, and the [`InteractableArea` component](#system-hud-insets-interactablearea) can keep UI clear of them automatically. * **The device's hardware-reserved margins** β€” the notch or camera cutout, status bar, home indicator, and rounded corners. The SDK keeps UI clear of these automatically with the [`ScreenInsetArea` component](#device-hardware-insets-screeninsetarea). The **mobile safe area** is what's left over β€” where scene UI can live without clashing with either. @@ -86,6 +86,35 @@ Scene UI that overlaps the reserved regions will: * Compete for taps with the system controls β€” players will accidentally trigger one or the other. * Make your scene feel broken on mobile, which hurts featuring and retention. +## System HUD insets (`InteractableArea`) + +The tables above are design-time guides: use them to plan your layout. At runtime, the explorer also **reports** the region that's currently free of its own UI β€” the **interactable area** β€” and the `InteractableArea` component constrains your UI to it automatically. Import it from `@dcl/sdk/react-ecs` and wrap the UI you want to protect: + +```tsx +import ReactEcs, { ReactEcsRenderer, UiEntity, InteractableArea } from '@dcl/sdk/react-ecs' +import { Color4 } from '@dcl/sdk/math' + +export function setupUi() { + ReactEcsRenderer.setUiRenderer(() => ( + + {/* A child sized 100% Γ— 100% fills the interactable area exactly */} + + + )) +} +``` + +`InteractableArea` positions itself absolutely using the inset values the explorer reports, so the `positionType` and `position` fields of its `uiTransform` are reserved β€” any value you set for them is ignored. All other `uiTransform` properties and UI components work as usual. The reported area can change at any moment β€” for example when the player opens or closes the chat β€” and the component reacts automatically. + +{% hint style="info" %} +**πŸ“± Mobile only (for now)**: Currently only the **mobile client** (Godot) reports an interactable area, so `InteractableArea` only prevents overlaps there. Other clients will adopt it soon. On clients that don't report a value, the insets are `(0, 0, 0, 0)` and the component covers the whole screen β€” safe to leave in cross-platform code, but not a substitute for testing. Always verify your layout on a real device using the [preview QR code](preview-on-mobile.md). +{% endhint %} + +See [Explorer UI insets](../2d-ui/ui-positioning.md#explorer-ui-insets-interactablearea) for more details on this component. + ## Device hardware insets (`ScreenInsetArea`) The reserved margins above belong to the Decentraland **system HUD**. Separately, the **device hardware** reserves screen space for the notch or camera cutout, the status bar, the home indicator, and rounded corners. UI drawn underneath these is partly hidden or hard to tap. diff --git a/creator/sdk7/building-for-mobile/ui-best-practices.md b/creator/sdk7/building-for-mobile/ui-best-practices.md index 44acd7aa..29d06847 100644 --- a/creator/sdk7/building-for-mobile/ui-best-practices.md +++ b/creator/sdk7/building-for-mobile/ui-best-practices.md @@ -9,7 +9,7 @@ There is no single proven recipe for Decentraland mobile UI yet β€” the platform ## DOs * **Design mobile-specific UIs**, or vary your UI by screen size and platform. Use [`isMobile()`](detect-platform.md) to branch. -* **Keep critical UI inside the [safe area](safe-area.md)**, and wrap it in [`ScreenInsetArea`](safe-area.md#device-hardware-insets-screeninsetarea) to clear the device's hardware margins (notch, status bar, home indicator). +* **Keep critical UI inside the [safe area](safe-area.md)**. Wrap it in [`InteractableArea`](safe-area.md#system-hud-insets-interactablearea) to clear the system HUD (joystick, chat, profile, action buttons) and in [`ScreenInsetArea`](safe-area.md#device-hardware-insets-screeninsetarea) to clear the device's hardware margins (notch, status bar, home indicator). * **Minimize options.** Show only what the player needs right now and progressively disclose the rest. * **Place actionable dialogs at the center of the screen** β€” anywhere a player needs to read and respond. * **Place non-actionable messages at the top-center** β€” status, notifications, and ambient information. From 182f4d06dd428f6c85b8ce52f0b62d90ea587c93 Mon Sep 17 00:00:00 2001 From: Nicolas Earnshaw Date: Tue, 7 Jul 2026 16:19:25 -0300 Subject: [PATCH 3/3] Update ui-positioning.md Signed-off-by: Nicolas Earnshaw --- creator/sdk7/2d-ui/ui-positioning.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/creator/sdk7/2d-ui/ui-positioning.md b/creator/sdk7/2d-ui/ui-positioning.md index ca1d6929..0212e5e7 100644 --- a/creator/sdk7/2d-ui/ui-positioning.md +++ b/creator/sdk7/2d-ui/ui-positioning.md @@ -370,7 +370,7 @@ Some other best practices regarding UI sizes: * If the width or height of any UI element is dynamic, it's good to also use the `maxWidth`, `minWidth`, `maxHeight`, and `minHeight` parameters to make sure they stay within reasonable values. * The font size of text is relative to a fixed number of pixels, you should make it dynamic so it remains readable on retina displays. See [Responsive text size](ui_text.md#responsive-text-size) -## Explorer UI insets (`InteractableArea`) +## Safe Screen Areas The Decentraland explorer draws its own UI on top of every scene: the minimap, the chat window, the profile widget, and other overlays. These elements always render above scene UI, so anything you place under them is partly hidden and can't be clicked. How much of the screen they take up depends on the platform, and can change at any moment β€” for example when the player opens or closes the chat. The portion of the screen that's free of explorer UI is called the **interactable area**.