Render server-driven SwiftUI interfaces from JSON across Apple platforms.
DynamicUI turns versioned JSON component trees into native SwiftUI views. Use it for remote forms, configurable extension interfaces, feature-driven layouts, and rapid prototypes without giving up native controls or accessibility semantics.

- Native SwiftUI views on iOS, macOS, tvOS, watchOS, Mac Catalyst, and visionOS.
- Nested layouts, interactive controls, conditional content, and runtime value updates.
- Versioned schemas with validation before rendering.
- Application-defined components through a custom renderer.
- Explicit VoiceOver, Voice Control, and UI-test metadata in JSON.
- Swift 5.9+ (Xcode 15+)
- iOS 15+, macOS 12+, tvOS 14+, watchOS 8+, Mac Catalyst 15+, visionOS 1.0+
Add DynamicUI using Swift Package Manager:
dependencies:[.package(url:"https://github.com/0xWDG/DynamicUI.git", exact:"0.1.1"),],targets:[.target(name:"MyTarget", dependencies:[.product(name:"DynamicUI",package:"DynamicUI"),]),]In Xcode, select File → Add Package Dependencies, enter
https://github.com/0xWDG/DynamicUI, and choose version 0.1.1.
Then import the package:
import DynamicUIimport SwiftUI
import DynamicUI
structContentView:View{letjson=""" [ {"type": "Text","title": "This interface comes from JSON","modifiers": {"foregroundStyle":"red","opacity":0.6} }, {"type": "Button","title": "Continue","eventHandler": "continue" }, {"type": "Toggle","title": "Show details","identifier": "showDetails" } ]"""@Stateprivatevarcomponent:DynamicUIComponent?@Stateprivatevarerror:Error?varbody:someView{DynamicUI(json: json, component: $component, error: $error)}}For production payloads, use the versioned layout envelope:
{
"schemaVersion": 1,
"components": [
{ "type": "Text", "title": "A validated layout" }
]
}Legacy top-level component arrays remain supported. Validate either representation before
rendering with try DynamicUILayout(json: json). Validation rejects unsupported schema versions,
empty types and identifiers, duplicate identifiers, and malformed component conditions.
Conditional expressions can select strings or control whether an entire component renders:
[
{
"type": "Toggle",
"title": "Show favorite",
"identifier": "favorite"
},
{
"type": "Label",
"title": "{$favorite ? Saved : Not saved}",
"url": "{$favorite ? star.fill : star}"
},
{
"type": "Text",
"title": "The favorite is enabled",
"if": "$favorite"
}
]The string syntax is {$identifier ? valueWhenTrue : valueWhenFalse}. Missing identifiers and
empty, zero, false, or null values select the false branch and hide conditional views.
The component binding receives the latest interacted-with component. Stateful controls include
their new value in state; identifier and eventHandler let your application route the update.
.onChange(of: component){ component inguardlet component else{return}print(component.identifier asAny)print(component.eventHandler asAny)print(component.state asAny)}You can use a callback instead of a binding:
DynamicUI(json: json){ component inprint(component.eventHandler asAny, component.state asAny)}Render application-specific component types without forking DynamicUI. Return nil for component
types your renderer does not recognize:
DynamicUI(
json: json,
component: $component,
error: $error,
customViewRenderer:{ component inguard component.type =="ProductCard"else{returnnil}returnAnyView(ProductCard(
title: component.title ??"Product",
productID: component.parameters?["productID"]?.toString()))})DynamicUI applies supported modifiers and accessibility metadata to the custom view. Unknown types that neither DynamicUI nor the application recognizes are logged and skipped.
DynamicUI uses native SwiftUI controls and supports explicit assistive-technology metadata:
{
"type": "Button",
"title": "Save",
"accessibilityLabel": "Save profile",
"accessibilityHint": "Saves your profile changes",
"accessibilityValue": "Ready",
"accessibilityIdentifier": "profile.save",
"accessibilityInputLabels": ["Save", "Save profile"]
}Use accessibilityHidden: true only for decorative content. Visible control titles continue to
provide native semantics when explicit accessibility metadata is not needed. Accessibility string
fields support the same conditional expressions as visible strings.
Every component requires a case-sensitive type. Common optional fields are:
| Field | Purpose |
|---|---|
title | Label, title, placeholder, or image description |
if | Identifier condition such as $showDetails that controls rendering |
identifier | Stable key for updates and conditional expressions |
eventHandler | Application-defined event name returned on interaction |
defaultValue | Initial value for stateful controls |
children | Nested component array for containers |
url | SF Symbol name or URL, depending on the component |
disabled | Disables the component |
modifiers | Visual and behavioral modifiers |
minimumValue, maximumValue | Numeric bounds for sliders and progress views |
accessibilityLabel | Concise, speakable name for assistive technologies |
accessibilityHint | Describes the result of interacting with the component |
accessibilityValue | Accessible state or formatted value |
accessibilityIdentifier | Stable identifier for UI automation |
accessibilityHidden | Hides decorative content from assistive technologies |
accessibilityInputLabels | Alternative spoken names for Voice Control |
Unknown component types are logged and skipped, allowing valid sibling components to keep
rendering. Decode and validation failures are written to the optional error binding and display a
fallback error view. Objects without a string type field are treated as metadata and ignored in
component arrays.
The Playground directory contains an Xcode project with basic and exhaustive JSON examples for
macOS, iOS, watchOS, tvOS, and visionOS.
See the complete schema, platform behavior, component examples, and modifier reference in the documentation.
- Aurora Editor for custom views in extensions.
Using DynamicUI in your project? Open a pull request to add it here.
Issues and pull requests are welcome. See CONTRIBUTING.md for setup, testing,
and contribution guidance, or browse the
good first issue label.
🦋 @0xWDG 🐘 mastodon.social/@0xWDG 🐦 @0xWDG 🧵 @0xWDG 🌐 wesleydegroot.nl 🤖 Discord