Ultralight is a custom fork of WebKit (really, just the "WebCore" and "JavaScriptCore" modules) that began in 2014.

The latest version (2.0) is based off a build of WebKit from 2023, and most modern web features work as you would expect in recent Safari versions.

## WebKit / Safari Version

Ultralight 2.0 tracks WebKit 615.1.18 (Safari 16.4). The version is readable at runtime through [WebKitVersionString()](/api/cpp/2_0_0/_ultralight_2_defines_8h.html#ac65cc8f2e9c3076446cb9d0953e3745f).

Each row below marks a feature **Supported**, **Partial** (it works with a caveat in the notes), or **Not available**.

## HTML

| Feature | Status | Notes |
|---|---|---|
| HTML5 parsing and elements | Supported | Works as it does in Safari. |
| Web components (custom elements, shadow DOM, `<slot>`) | Supported | |
| `<dialog>` and `<details>` | Supported | |
| `contenteditable` | Supported | |
| `<input type="file">` | Partial | Opens no picker (`File` and `FileReader` work). |
| `loading="lazy"` on images | Not available | Ignored, so every image loads right away. |
| The `popover` attribute | Not available | Arrived after this WebKit generation. |
| MathML | Not available | |
| Date, time, month, week, and color inputs | Not available | They act as plain text inputs. |
| `<datalist>` | Not available | Shows nothing. |
| Spell checking | Not available | `spellcheck` has no effect. |

## CSS

| Feature | Status | Notes |
|---|---|---|
| Flexbox | Supported | |
| Grid and subgrid | Supported | |
| Custom properties | Supported | |
| Container queries | Supported | |
| Cascade layers | Supported | |
| Nesting | Supported | |
| `:has()` | Supported | |
| Filters and backdrop filters | Supported | |
| Blend modes | Supported | |
| `image-rendering: pixelated` and `crisp-edges` | Supported | Scaled images and pixel art keep hard pixel edges instead of being smoothed. |
| Transitions and animations | Supported | |
| `prefers-color-scheme` | Supported | Follows the scheme your app reports (see [Dark Mode and Color Schemes](/docs/2.0/dark-mode-and-color-schemes)). |
| `(hover: ...)` and `(pointer: ...)` media queries | Partial | They report no pointing device at all: `(hover: hover)` and `(pointer: fine)` never match, `(hover: none)` and `(pointer: none)` do. Don't gate a desktop layout on them— a responsive framework can end up serving its touch layout. |
| Image cursors (`cursor: url(...)`) | Partial | Ignored, though named cursors reach your app. |
| Smooth scrolling | Not available | `scroll-behavior: smooth` and the `behavior: 'smooth'` option jump straight to the target. |
| `hyphens: auto` | Not available | Never hyphenates. |
| View transitions | Not available | Arrived after this WebKit generation. |
| Anchor positioning | Not available | Arrived after this WebKit generation. |
| `@scope` | Not available | Arrived after this WebKit generation. |
| `text-wrap: balance` | Not available | Arrived after this WebKit generation. |

## Fonts and Images

| Feature | Status | Notes |
|---|---|---|
| `@font-face` web fonts | Supported | TrueType, OpenType, WOFF, and WOFF2. |
| Variable fonts | Supported | `font-weight`, `font-stretch`, `font-style`, and `font-variation-settings`. |
| PNG, JPEG, GIF, WebP, BMP, and ICO images | Supported | |
| SVG | Supported | Inline and as an image. |
| `.dds` textures in `<img>` and CSS | Supported | See [Compressed Textures](/docs/2.0/compressed-textures). |
| Textures straight from your app | Supported | See [Displaying Custom Textures](/docs/2.0/displaying-custom-textures). |
| AVIF and JPEG XL images | Not available | They don't decode. |

## JavaScript

The Free edition has JIT disabled (JavaScript may execute slower). The editions are covered in [Editions and Feature Macros](/docs/2.0/editions-and-feature-macros).

| Feature | Status | Notes |
|---|---|---|
| ES2022 | Supported | |
| The ES2023 array additions | Supported | |
| ES modules and dynamic `import()` | Supported | |
| Web Workers | Supported | |
| Secure contexts on local pages | Supported | Pages loaded from `file://` URLs are secure contexts, and so is HTML loaded from a string with a `file://` base URL. Without one, that HTML is `about:blank`, which is not, and secure-context-only APIs such as `crypto.randomUUID()` and `navigator.locks` disappear— so pass a base URL. |
| Switching JavaScript off per View | Supported | The page still lays out and animates, and your app drives it from native code (see [Embedded and Device UI](/docs/2.0/embedded-and-device-ui)). |
| `Object.groupBy`, the RegExp `v` flag, `Temporal` | Not available | Anything later than the Safari 16.4 level. |
| WebAssembly | Not available | Pick the JavaScript build of any library that offers one. |
| `SharedArrayBuffer` | Not available | |
| Shared workers | Not available | |
| Service workers | Not available | |
| `requestIdleCallback()` | Not available | Schedule background work with `setTimeout()` or `requestAnimationFrame()`. |

## Canvas and Graphics

| Feature | Status | Notes |
|---|---|---|
| Canvas 2D | Supported | Including `Path2D`, `roundRect()`, conic gradients, `ImageBitmap`, and `toBlob()`. |
| `imageSmoothingEnabled` and `imageSmoothingQuality` | Supported | Turning smoothing off keeps hard pixel edges when scaling. `'high'` applies a higher-quality filter when enlarging. `'low'` and `'medium'` use the default. |
| `OffscreenCanvas` | Supported | On the page and in workers. |
| The canvas `filter` property | Partial | Works unless your app switches canvas filters off. Reference filters like `url(#id)` draw unfiltered. |
| Canvas readback on a GPU-rendered View | Partial | `getImageData()` throws, `toDataURL()` returns `data:,`, and `putImageData()` does nothing. Create the context with `getContext('2d', { willReadFrequently: true })` and all three work. CPU-rendered Views are unaffected. |
| WebGL | Not available | `getContext('webgl')` returns `null`. Draw 3D content in your engine and show it on the page as a custom texture (see [Displaying Custom Textures](/docs/2.0/displaying-custom-textures)). |
| WebGPU | Not available | |

## Media

| Feature | Status | Notes |
|---|---|---|
| HTML5 video and audio | Partial | Pro edition and higher, from the WebM container with VP9 video and Opus audio (in lower editions media playback isn't available). See [Media and Audio](/docs/2.0/media-and-audio). |
| `autoplay` | Supported | Works without a user gesture. |
| The `controls` attribute | Supported | |
| `<track>` subtitles | Supported | |
| Media Source Extensions | Not available | An HLS or DASH player won't work— point the element at a WebM file instead. |
| Web Audio | Not available | |
| WebRTC and `getUserMedia()` | Not available | |
| DRM (Encrypted Media) | Not available | |
| Picture-in-picture | Not available | |
| WebCodecs | Not available | |

## Networking

| Feature | Status | Notes |
|---|---|---|
| `fetch()` and `XMLHttpRequest` | Supported | Requests go over HTTP/1.1 or HTTP/2. |
| Streaming response bodies | Supported | |
| `EventSource` | Supported | |
| `WebSocket` | Supported | |
| Content Security Policy | Supported | Enforced. |
| Downloads | Partial | A file the server sends as an attachment, or one the page can't display, reaches your app as a download (see [Downloads and Network Control](/docs/2.0/downloads-and-network-control)). An `<a download>` link just navigates. |
| `navigator.sendBeacon()` | Not available | Send analytics with `fetch()` while the page is alive. |

## Storage and Crypto

| Feature | Status | Notes |
|---|---|---|
| Cookies | Supported | They live in the View's session (see [Sessions and Site Data](/docs/2.0/sessions-and-site-data)). |
| `localStorage` and `sessionStorage` | Supported | In the View's session. |
| IndexedDB | Supported | In the View's session. |
| `BroadcastChannel` | Supported | |
| `navigator.locks` | Supported | |
| `crypto.getRandomValues()` and `crypto.randomUUID()` | Supported | |
| `crypto.subtle` | Not available | Hashing and signing need a JavaScript library. |
| The Cache API | Not available | |
| `navigator.storage` | Not available | |
| `CompressionStream` | Not available | |

## Forms and Input

| Feature | Status | Notes |
|---|---|---|
| Mouse, wheel, keyboard, and pointer events | Supported | From the input your app forwards (see [Mouse and Scroll Input](/docs/2.0/mouse-and-scroll-input) and [Keyboard Input](/docs/2.0/keyboard-input)). |
| The Gamepad API | Supported | From the controllers your app forwards (see [Gamepad Input](/docs/2.0/gamepad-input)). |
| Form validation | Partial | An invalid control blocks the submission. `invalid` fires on each bad control and the first takes focus, but no message bubble is drawn, so style `:invalid` or show your own text (see [Forms and Inputs](/docs/2.0/forms-and-inputs)). |
| Touch events | Not available | Use pointer events. |
| HTML drag and drop | Not available | `draggable` never starts a drag. Use pointer events. |

## Clipboard

| Feature | Status | Notes |
|---|---|---|
| Copy and cut | Supported | From script and the keyboard. |
| Script-initiated paste | Partial | Works only during a click or key press and only when your app's clipboard policy allows it (see [Text Editing and the Clipboard](/docs/2.0/text-editing-and-clipboard)). |
| Text and HTML on the clipboard | Supported | |
| Images and files on the clipboard | Not available | |
| `navigator.clipboard` | Not available | Use `document.execCommand()`. |

## The Window and Devices

| Feature | Status | Notes |
|---|---|---|
| `devicePixelRatio` | Supported | Reflects the View's scale. |
| `window.screen` | Partial | Reports zero for its size, so size your layout from `innerWidth` / `innerHeight` or viewport units. |
| `window.open()` and `target="_blank"` | Partial | They open a new View only when your app provides one, and `window.open()` returns `null` otherwise (see [Handling View Events](/docs/2.0/handling-view-events)). |
| `alert()`, `confirm()`, and `prompt()` | Not available | `alert()` shows nothing, `confirm()` returns `false`, and `prompt()` returns `null`. Build dialogs in the page or natively (see [Popups and Dialogs](/docs/2.0/popups-and-dialogs)). |
| `beforeunload` prompts | Not available | `beforeunload` never asks. |
| `window.print()` | Not available | |
| `requestFullscreen()` | Not available | Your app owns the window (see [Windows, Monitors, and DPI](/docs/2.0/windows-monitors-and-dpi)). |
| `requestPointerLock()` | Not available | |
| `Notification` | Not available | |
| `navigator.geolocation` | Not available | |
| Permissions, Web Share, wake locks, Payment Request, Web Authentication, speech, WebXR | Not available | |
| Assistive technology | Not available | Pages are not exposed to screen readers (ARIA attributes parse but reach nothing). |

## Checking for a Feature

Feature-detect the usual way, with `typeof`, `'x' in navigator`, or `CSS.supports()`. A missing API is simply undefined, with no other message.

```js
if ('requestIdleCallback' in window)
  requestIdleCallback(saveDraft);
else
  setTimeout(saveDraft, 0);
```
