Embedded and Device UI
Configure the CPU renderer, disable JavaScript, and run lightweight interfaces on resource-constrained devices.
On this page
You can build UI for a kiosk, an automotive display, a set-top box, or other low-power hardware using the same HTML and CSS you write for the desktop. Ultralight lets you tune its footprint to fit what your device can afford.
📘 Framebuffer Tutorial
Rendering to a Framebuffer walks through displaying your first page on a device step by step. This page covers best-practices and links to relevant guides for each subsystem.
Best-Practices
- Draw directly to the framebuffer. Use the CPU renderer to paint directly into your display framebuffer or compositor plane. You can provide raw destination buffers by registering a custom
SurfacewithPlatform::set_surface_factory()— see Render Surfaces. - Use GPU rendering when available. If the device has a capable GPU with dedicated VRAM, the GPU renderer should be used (along with DDS image assets) for maximum performance.
- Avoid resizing Views. Create the View at the display panel's physical pixel dimensions and avoid resizing it.
- Avoid repainting unchanged Views. After rendering a View, you should only present when
Surface::dirty_bounds()is not empty to avoid unnecessary redraws— see Updating and Rendering. - Avoid using JavaScript. Use the DOM or Data Bindings API for updating your UI from native code instead of JavaScript. See below.
Updating the Page Without JavaScript
The library's native DOM and Data Bindings API work with scripts disabled and should be used when performance is a critical concern.
Use data bindings for values and collections that update often, such as sensor readouts, status indicators, or channel lists. You bind a native struct on your thread, modify it freely, and call Sync() each tick to push only what changed to the page— see About Data Bindings.
Use the DOM API to modify document structure, update CSS styles, and listen for events directly from C++.— see About the DOM API.
Optimizing for Memory
Set Config::memory_profile = MemoryProfile::LowMemory before creating the Renderer. Under this profile, allocations pack densely, freed heap memory returns to the OS promptly, and garbage collection runs eagerly— see Managing Memory. The profile is read when the Renderer is created and by each View at its creation.
Using GPU Acceleration
If your device has a capable GPU, set ViewConfig::is_accelerated = true and provide your own GPUDriver implementation— see GPU Renderer Overview.
Using block-compressed textures for UI images reduces GPU memory usage and avoids CPU decode overhead (Pro edition or higher)— see Compressed Textures.
Custom File Loader
To load pages, stylesheets, and images from an archive file or directly from memory, implement a custom FileSystem and register it with Platform::set_file_system()— see Custom File System.
Bundling Fonts
If the device has no system fonts, provide bundled font files by implementing a custom FontLoader and registering it with Platform::set_font_loader()— see Custom Font Loading.