LeCodesdocs

UI content elements

The leaf elements that display something: UIText, UIImage, UIVideo. Each has one mutable content property (.text, .src, .player) that lives on the element — content is never a style. For editable text see UIInput in interactive.md.

At a glance

TypeScript
import photo from './photo.jpg'

const caption = UIText("Loading…").style({ color: "white", fontSize: 16 })

const screen = UIScreen(
  UIImage(photo).style({ width: "100%", height: 220, borderRadius: 16, objectFit: "cover" }),
  caption,
).style({ bgColor: "black", p: 16, pt: "safe-top", gap: 12 })

screen.open()
caption.text = "Sunset over the harbor"     // re-renders immediately

UIText

TypeScript
UIText(text: string): UIText

text.text = "Updated"       // get/set the displayed string

Text-specific styles (on top of the common element styles):

TypeScript
textAlign:      "start" | "center" | "end" | "left" | "right"   // default left
fontFamily:     string            // system: "serif", "sans-serif", "monospaced"; custom via font() (see fonts.md)
fontSize:       UIValue           // default 14
lineHeight:     UIValue | "normal"
fontWeight:     number | "normal" | "bold"     // 400, 700, …
fontStyle:      "normal" | "italic"
color:          Color
textDecoration: "underline" | "line-through" | "none"
letterSpacing:  UIValue
lineClamp:      number            // cap at N lines; 0 / omitted = unbounded
textOverflow:   "ellipsis" | "clip"            // default ellipsis
Note

lineHeight: 1.5 means 1.5 px — a bare number is px everywhere. A multiplier is "1.5em" (resolved against this element's own fontSize; there is no inheritance).

Clamping text to N lines

lineClamp caps the text at N lines, and the element is measured to that height — so the surrounding layout reflows around the clamped box rather than around the full string:

TypeScript
UIText(article.summary).style({ lineClamp: 3 })                        // 3 lines, then "…"
UIText(article.summary).style({ lineClamp: 3, textOverflow: "clip" })  // 3 lines, hard cut

textOverflow only takes effect together with lineClamp — with no clamp the box is sized to the whole string, so nothing overflows. "clip" is approximate on the web: it cuts on the line-height box rather than on the glyph, so a descender on the last line may be shaved differently than on iOS. "ellipsis" is exact on both.

Note

the default screen background is black — always set color explicitly.

UIText's .style() takes no border/background-image props — bgColor, padding, and margins work, but a decorated text chip is a UIRow/UIColumn around a UIText. Custom fonts must be registered before the screen opens — see fonts.md.

UIImage

TypeScript
UIImage(src: ImageSource): UIImage

// ImageSource:
//   string          — remote URL, or an asset() / imported project file
//   FetchResponse   — a downloaded body used directly as pixels
//   File            — from openFilePicker()
//   SvgSource       — wrapped raw SVG XML
//   Canvas          — a 2D Canvas, baked to a texture on assign

img.src            // get the (resolved) source / set a new one — updates the displayed image

Image-specific styles:

TypeScript
objectFit:    "cover" | "contain" | "fill"   // how the source maps into the box
tintColor:    Color                          // SVG sources only
borderRadius: number                         // px (number only on UIImage)
Note

tintColor applies only to SVG sources, and it replaces all fill and stroke colors in the SVG — it's for monochrome icons, not multicolored artwork.

Note

a bare relative path string (UIImage("./photo.png")) does not resolve to a bundled asset — plain strings work only for https://… URLs. Import the file or use asset('./photo.png').

Icons: assetIcon("pack:name")

For icons, prefer the assetIcon() compile macro over hand-writing SVG: it resolves one icon from the icon registry at build time and inlines it as an image source (the same shape SvgSource returns), so it renders offline and identically on every host — no network at render time.

TypeScript
UIImage(assetIcon("lucide:bell")).style({ width: 24, height: 24, tintColor: "#8a8f98" })
UIImage(assetIcon("lucide:check", { color: colors.accent })).style({ width: 20, height: 20 })

The "pack:name" id must be a string literal (an unknown id is a compile error; browse packs at https://icon-registry.jt3.ru). Recolor with the { color } option — a hex literal is baked into the SVG at compile time, a token/expression is applied as a tint — or with the tintColor style prop.

Cropping an atlas: setSourceRect

TypeScript
img.setSourceRect(x: number, y: number, w: number, h: number): this   // texture pixels

Renders only a sub-rectangle of the source — the spritesheet primitive. The rect's w/h become the element's intrinsic size (one frame, not the whole atlas), and the cropped frame always fills the box, overriding objectFit. Chainable and safe to call before the element is on screen (the initial crop is applied at mount). Sprite-style animation is swapping the rect per frame:

TypeScript
const icon = UIImage(atlasUrl).style({ width: 64, height: 64 }).setSourceRect(0, 0, 128, 128)
let frame = 0
setInterval(() => { icon.setSourceRect((++frame % 8) * 128, 0, 128, 128) }, 100)

Pitfall: bgImage is not image content

TypeScript
const img = UIImage("")            // ✗ empty source as a placeholder
img.style.bgImage = url            // ✗ bgImage is container decoration, not content
const img = UIImage(url)           // ✓ the source goes in the constructor…
img.src = newUrl                   // ✓ …and swaps via .src

UIVideo

TypeScript
UIVideo(player: VideoPlayer): UIVideo

video.player       // read-only — the VideoPlayer passed at construction

The element is just the on-screen surface; playback lives entirely on the VideoPlayer — create it first, control it directly:

TypeScript
const player = new VideoPlayer(asset('./intro.mp4'))
const video = UIVideo(player).style({ width: "100%", height: 220, borderRadius: 12, objectFit: "cover" })
player.play()

Video-specific style:

TypeScript
objectFit: "cover" | "contain" | "fill"

UIVideo also accepts the drawable styles (border*, bgColor, …) for framing. The player is a native resource — dispose() it when the video is gone for good, and stop playback in the screen's onClose (see Conventions — Lifecycle).

See also