Skip to content

Instantly share code, notes, and snippets.

@bcardarella
Created September 9, 2025 15:04
Show Gist options
  • Save bcardarella/80f46ea6e40efcb2e69fdd0f856f5666 to your computer and use it in GitHub Desktop.
Save bcardarella/80f46ea6e40efcb2e69fdd0f856f5666 to your computer and use it in GitHub Desktop.

View Markup Language (VML) Specification

Version 1.0
Status: Definitive Specification
Date: 2025-09-05

Abstract

This specification defines the View Markup Language (VML), a declarative SGML-based markup language for describing SwiftUI user interface hierarchies. VML provides a static, server-renderable representation of SwiftUI views that can be transmitted over HTTP and interpreted by compliant clients.

Table of Contents

  1. Introduction
  2. Conformance
  3. Document Structure
  4. Elements
  5. Attributes
  6. The Style Attribute
  7. Templates and Slots
  8. The attr() Function
  9. Serialization Rules
  10. Core View Catalog
  11. Modifiers Reference
  12. Custom Extensions
  13. Grammar Definition
  14. Examples

1. Introduction

1.1 Purpose

VML is designed to provide a static, declarative representation of SwiftUI view hierarchies suitable for server-side generation and client-side rendering. It establishes a precise mapping between SwiftUI's programmatic API and an SGML-based markup format.

1.2 Scope

This specification covers:

  • Declarative description of SwiftUI-compatible UI hierarchies
  • Elements corresponding to SwiftUI view types
  • Attributes representing SwiftUI initializer parameters
  • Style serialization for SwiftUI view modifiers
  • Template/slot mechanism for ViewBuilder arguments
  • Attribute binding through the attr() function

1.3 Out of Scope

The following are explicitly excluded from VML:

  • Runtime closures, callbacks, or event handlers
  • Control flow structures (if, switch, ForEach)
  • Data bindings or state management
  • Business logic or computations (except within style expressions)
  • Dynamic interpolation within markup

1.4 Design Principles

  1. Static Declaration: VML documents are completely static. All values MUST be resolved at generation time.
  2. Direct Mapping: VML elements map 1:1 to SwiftUI view types, preserving exact naming and casing.
  3. No Runtime Evaluation: VML contains no executable code, closures, or runtime bindings.
  4. Predictable Serialization: Modifier order and formatting follow strict, deterministic rules.

2. Conformance

2.1 Document Conformance

A conforming VML document:

  • MUST use the <!doctype swiftui+vml> declaration
  • MUST have a root <vml> element
  • MUST quote all attribute values with either double quotes or single quotes
  • MUST NOT contain runtime interpolation or bindings
  • MUST follow the serialization rules defined in Section 9

2.2 Client Conformance

A conforming VML client:

  • MUST recognize all core SwiftUI view elements defined in Section 10
  • MUST process the style attribute according to Section 6
  • MUST support the template/slot mechanism defined in Section 7
  • MUST handle the attr() function as specified in Section 8
  • MAY support custom elements and modifiers through registration

3. Document Structure

3.1 Basic Structure

<!doctype swiftui+vml>
<vml>
  <head>
    <!-- Optional external styles and lifecycle templates -->
  </head>
  <body>
    <!-- Root view hierarchy -->
  </body>
</vml>

3.2 HTTP Headers

Request:

ACCEPT: application/swiftui+vml[; target=ios|macos|visionos]

Response:

CONTENT-TYPE: application/swiftui+vml

The target parameter indicates the requesting device platform and is available to developers for programmatic decision-making on the server. VML does not enforce how or whether the target parameter should be used. If omitted, iOS is the default target. Clients typically send the appropriate target value when connecting.

Note: Version negotiation support is planned for future releases due to framework compatibility requirements but is not yet specified.

3.3 Head Section

The head section MAY contain:

  • External View Style Sheet (VSS) references: <Style url="/path/to/styles.vss"/>
  • Lifecycle templates for application state management

External VSS files are fetched from the specified URL. The VSS format and processing rules are defined in a separate specification.

<head>
  <Style url="/styles/main.vss"/>
  <VStack template="launch" spacing="16">
    <Image systemName="app.badge"/>
    <Text style="font(.headline)">Starting Up</Text>
  </VStack>
  <VStack template="connecting" spacing="16">
    <ProgressView/>
    <Text style="font(.headline)">Connecting</Text>
  </VStack>
  <VStack template="disconnected" spacing="16">
    <Image systemName="cloud.slash"/>
    <Text style="font(.headline)">Connection Lost</Text>
  </VStack>
  <VStack template="error" spacing="16">
    <Image systemName="exclamationmark.triangle.fill"/>
    <Text style="font(.headline)">An Error Occurred</Text>
  </VStack>
</head>

3.3.1 Lifecycle Templates

The following lifecycle template names have special meaning when present in the head section:

  • launch - App starting up, before any connection attempt
  • connecting - Attempting to establish or re-establish connection
  • error - Connection or loading failed
  • disconnected - Connection lost, not attempting to reconnect

These templates are parsed by the client and used for application state management when server-provided content is not available. Template names used in the body section do not have special lifecycle meaning.

4. Elements

4.1 Element Names

Element names MUST exactly match their corresponding SwiftUI view type names, preserving case sensitivity.

Examples:

  • Text<Text>
  • VStack<VStack>
  • RoundedRectangle<RoundedRectangle>
  • NavigationStack<NavigationStack>

4.2 Element Categories

4.2.1 Text Elements

<Text>Content</Text>
<Label title="Title" systemImage="star"/>
<Label>Title</Label>  <!-- Alternative form -->

4.2.2 Layout Containers

<VStack alignment="leading" spacing="12">...</VStack>
<HStack alignment="center" spacing="8">...</HStack>
<ZStack alignment="topLeading">...</ZStack>
<Group>...</Group>
<ScrollView axis="horizontal">...</ScrollView>

4.2.3 Shapes

<Circle/>
<Rectangle/>
<RoundedRectangle cornerRadius="12"/>
<Capsule/>
<Ellipse/>

4.2.4 Controls

<Button>Label</Button>
<Toggle>Setting</Toggle>
<TextField>Placeholder</TextField>
<Slider value="0.5" in="0...1"/>
<Picker selection="option1">...</Picker>

4.2.5 Custom Elements

Custom SwiftUI views are represented as elements with their type name:

<ProfileCard name="John Doe" role="Developer"/>
<CustomButton variant="primary">Click</CustomButton>

5. Attributes

5.1 Attribute Mapping

SwiftUI initializer parameters map directly to element attributes. The attribute name MUST match the SwiftUI parameter name exactly.

SwiftUI:

Image(systemName: "star.fill")
VStack(alignment: .leading, spacing: 12)
RoundedRectangle(cornerRadius: 10)

VML:

<Image systemName="star.fill"/>
<VStack alignment="leading" spacing="12"/>
<RoundedRectangle cornerRadius="10"/>

5.2 Attribute Value Types

5.2.1 Strings

String values MUST be enclosed in either double quotes or single quotes. When using double quotes, inner double quotes MUST be escaped as &quot;. When using single quotes, inner single quotes MUST be escaped as &#39;.

<Text message="Hello &quot;World&quot;"/>
<Text message='Hello "World"'/>
<Text message='Don&#39;t stop'/>

5.2.2 Numbers

Numeric values are represented as strings but interpreted as numbers by the client.

<Circle radius="50"/>
<VStack spacing="12"/>

5.2.3 Booleans

Boolean values are represented as "true" or "false" strings.

<Toggle isOn="true"/>
<ScrollView showsIndicators="false"/>

5.2.4 Enums

Enum values in attributes support both forms:

  • With leading dot: alignment=".leading"
  • Without leading dot: alignment="leading"

Both forms MUST be accepted by conforming clients.

<VStack alignment=".leading"/>  <!-- Valid -->
<VStack alignment="leading"/>   <!-- Also valid -->

5.2.5 Ranges

Range values use Swift range syntax:

<Slider in="0...1"/>           <!-- Closed range -->
<Slider in="0..<100"/>         <!-- Half-open range -->

5.2.6 Special Attributes

The following attributes have special handling:

  • id: View identity, derived from .id() modifier
  • tag: View tag for selection, derived from .tag() modifier
  • template: Declares the element as a template for a slot
<Text id="header-text" tag="main">Content</Text>
<Button template="action">OK</Button>

5.3 Omitted Attributes

When a SwiftUI initializer parameter has no meaningful static value, the attribute MUST be omitted entirely rather than provided with an empty or placeholder value.

SwiftUI:

TextField("Placeholder", text: $binding)  // Binding has no static value

VML:

<TextField>Placeholder</TextField>  <!-- text attribute omitted -->

6. The Style Attribute

6.1 Purpose

The style attribute contains all SwiftUI view modifiers that are not initializer parameters, serialized as a semicolon-separated list.

6.2 Syntax Rules

  1. Modifiers are separated by ; (semicolon, no space required)
  2. Labeled arguments use : (colon followed by single space)
  3. Modifiers are applied in the order they appear (left-to-right); VML does not enforce ordering constraints
  4. Enum values MUST include the leading dot
  5. Nested function calls and method chaining are permitted
  6. No leading whitespace is permitted within parentheses
  7. Whitespace is required after commas and after named argument colons
  8. Array elements within square brackets [...] use comma separation
  9. String values that may break parsing SHOULD use attr() function with attribute storage, or be entity-encoded if included directly in modifiers

6.3 Examples

SwiftUI:

Text("Hello")
    .font(.system(size: 17, weight: .semibold))
    .foregroundStyle(.blue)
    .padding(.horizontal, 12)
    .background(.yellow)

VML:

<Text style="font(.system(size: 17, weight: .semibold));foregroundStyle(.blue);padding(.horizontal, 12);background(.yellow)">Hello</Text>

6.2.1 Parsing Examples

Valid syntax:

style="background(.linearGradient(colors: [.blue, .red], startPoint: .top));padding(.horizontal, 16)"
style="fill(.radialGradient(colors: [.blue.opacity(0.8), .purple.mix(with: .red, by: 0.5)], center: .center))"

Invalid syntax:

style="padding( .horizontal , 16 )"  <!-- Leading whitespace in parentheses -->
style="font(.system(size:17))"  <!-- Missing space after colon -->

Complex string values:

<!-- Preferred: Use attr() for complex strings -->
<Text imageName="my-image, with comma.png" style="background(.image(attr(imageName)))">Text</Text>

<!-- Alternative: Entity encoding -->
<Text style="background(.image(&quot;my-image&comma; with comma.png&quot;))">Text</Text>

6.4 Value Types in Style

6.4.1 Simple Values

Colors, materials, and other simple values are written inline:

style="background(.blue)"
style="background(.ultraThinMaterial)"
style="foregroundStyle(.red.opacity(0.5))"

6.4.2 Gradients

Gradients use enum-style notation in VML for client processing:

style="fill(.linearGradient(colors: [.blue, .purple], startPoint: .top, endPoint: .bottom))"
style="fill(.radialGradient(colors: [.red, .orange], center: .center, startRadius: 10, endRadius: 100))"
style="fill(.angularGradient(colors: [.purple, .pink, .orange, .purple], center: .center))"

Note: The dot prefix notation (e.g., .linearGradient, .angularGradient) is VML-specific syntax that the client converts to the corresponding SwiftUI types (LinearGradient, RadialGradient, AngularGradient). When reverse-engineering VML to SwiftUI source code, these should be written without the dot prefix as struct initializers.

6.4.3 Shapes

Shapes use enum-style representation:

style="clipShape(.circle)"
style="clipShape(.rect(cornerRadius: 12))"

6.4.4 Complex Views

Views that require construction use template slots (see Section 7):

style="overlay(content: :overlayContent)"

6.5 Deprecated Modifiers

The .cornerRadius() modifier is deprecated. Use .clipShape(.rect(cornerRadius:)) instead.

Deprecated:

style="cornerRadius(8)"

Preferred:

style="clipShape(.rect(cornerRadius: 8))"

7. Templates and Slots

7.1 Overview

Templates provide a mechanism for supplying view content to modifiers that accept @ViewBuilder closures in SwiftUI.

7.2 Declaration

Templates are declared as immediate children with the template attribute:

<ParentView style="modifier(content: :slotName)">
  <ChildView template="slotName">Content</ChildView>
</ParentView>

7.2.1 Template Scoping

  • Parent elements ONLY render named slot templates of direct children
  • Templates that are not direct children are not accessible to parent modifiers
  • If a template is declared but not referenced by any modifier, it is ignored

7.3 Slot Naming

Template names MUST match the SwiftUI API parameter name:

SwiftUI Modifier Parameter Name Template Attribute
overlay(alignment:content:) content template="content"
background(alignment:content:) content template="content"
tabItem(_:) (unnamed) template="tabItem"
safeAreaInset(edge:content:) content template="content"
.sheet(isPresented:content:) content template="content"

7.4 Reference Syntax

Templates are referenced in the style attribute using the :name syntax:

style="overlay(alignment: .topLeading, content: :badge)"

The symbol syntax (:name) takes highest parsing priority and is always interpreted as a template reference, regardless of other syntactic patterns that may be present.

7.5 Implicit Slots

Some modifiers support implicit slot names where the slot name doesn't need to be referenced:

<View style="clipShape()">
  <Circle template="shape"/>
</View>

7.6 Multi-valued Slots

Some modifiers may act upon multiple templates with the same name:

<View style="toolbar(content: :content)">
  <ToolbarItem template="content" placement="navigationBarLeading">
    <Button>Back</Button>
  </ToolbarItem>
  <ToolbarItem template="content" placement="navigationBarTrailing">
    <Button>Done</Button>
  </ToolbarItem>
</View>

VML does not enforce multi-valued slot behavior. The SwiftUI client determines whether to collect all templates with the same name or use only one.

7.7 Template Examples

SwiftUI:

Text("Hello")
    .overlay(alignment: .topTrailing) {
        Circle()
            .fill(.red)
            .frame(width: 10, height: 10)
    }

VML:

<Text style="overlay(alignment: .topTrailing, content: :indicator)">Hello
  <Circle template="indicator" style="fill(.red), frame(width: 10, height: 10)"/>
</Text>

8. The attr() Function

8.1 Purpose

The attr() function binds element attribute values into style modifiers, enabling client-side value substitution.

8.2 Syntax

attr(name)
attr(name, fallback)
attr(name type(<type>))
attr(name type(<type>), fallback)

8.3 Type Hints

Supported type hints:

  • <string> - String values
  • <number> - Numeric values (float or integer)
  • <integer> - Integer values only
  • <length> - Length values with units
  • <angle> - Angle values (.degrees() or deg suffix)
  • <color> - Color values (SwiftUI or CSS format)
  • <url> - URL strings
  • <boolean> - Boolean values

8.4 Attribute Resolution

  1. attr() ONLY resolves attributes on the same element
  2. If the attribute exists (even as empty string), its value is used
  3. If the attribute is absent, the fallback is used (if provided)
  4. If no fallback and attribute is missing, the value returned is nil and client SHOULD log a warning

8.5 Examples

<!-- Using attribute value in modifier -->
<Circle fill="#ff0000" style="fill(attr(fill type(<color>)))"/>

<!-- With fallback -->
<Text size="20" style="font(.system(size: attr(size type(<number>), 17)))">Hello</Text>

<!-- For presentation state -->
<View showingSheet="false" style="sheet(isPresented: attr(showingSheet), content: :sheet)">
  <Text template="sheet">Sheet Content</Text>
</View>

8.6 Restrictions

The attr() function:

  • MUST NOT be used for template references
  • MUST NOT reference attributes from other elements
  • MUST NOT be used outside the style attribute

9. Serialization Rules

9.1 Formatting Requirements

  1. Spacing:

    • Between modifiers: ; (semicolon, no space required)
    • Between arguments: , (comma + single space)
    • Label-value separator: : (colon + single space)
  2. Attribute Order (RECOMMENDED):

    • id attribute (if present)
    • Initializer attributes (alphabetical)
    • Data attributes for attr() (alphabetical)
    • style attribute (last)
  3. Modifier Order: VML does not enforce constraints on modifier order; modifiers are applied in the order they appear

9.2 Entity Encoding

The following characters MUST be encoded in attribute values:

  • <&lt;
  • >&gt;
  • &&amp;
  • "&quot; (when using double-quoted attributes)
  • '&#39; (when using single-quoted attributes)
  • ;&#59; (when used within modifier arguments or string literals, not as modifier separator)

When using double-quoted attributes, only double quotes within the attribute value need to be encoded as &quot;. When using single-quoted attributes, only single quotes within the attribute value need to be encoded as &#39;.

9.3 Whitespace Handling

  • Leading/trailing whitespace in text content is preserved
  • Whitespace between elements follows SGML rules
  • No whitespace normalization in attribute values

10. Core View Catalog

10.1 Text Views

10.1.1 Text

SwiftUI:

Text("Hello World")
    .font(.title)
    .foregroundStyle(.blue)
    .multilineTextAlignment(.center)

VML:

<Text style="font(.title), foregroundStyle(.blue), multilineTextAlignment(.center)">Hello World</Text>

10.1.2 Label

SwiftUI:

Label("Home", systemImage: "house")
Label("Files", image: "folder-icon")

VML:

<Label systemImage="house">Home</Label>
<Label title="Files" image="folder-icon"/>

10.2 Image Views

10.2.1 Image

SwiftUI:

Image(systemName: "star.fill")
    .resizable()
    .frame(width: 50, height: 50)
    .foregroundStyle(.yellow)

Image("photo")
    .resizable()
    .scaledToFit()

VML:

<Image systemName="star.fill" style="resizable(), frame(width: 50, height: 50), foregroundStyle(.yellow)"/>
<Image name="photo" style="resizable(), scaledToFit()"/>

10.2.2 AsyncImage

SwiftUI:

AsyncImage(url: URL(string: "https://example.com/image.jpg")) { phase in
    switch phase {
    case .empty:
        ProgressView()
    case .success(let image):
        image
            .resizable()
            .aspectRatio(contentMode: .fit)
    case .failure:
        Image(systemName: "photo")
    @unknown default:
        EmptyView()
    }
}

VML:

<AsyncImage url="https://example.com/image.jpg">
  <ProgressView template="phase.empty"/>
  <AsyncImage image template="phase.success" style="resizable(), aspectRatio(contentMode: .fit)"/>
  <Image systemName="photo" template="phase.failure"/>
</AsyncImage>

10.3 Layout Containers

10.3.1 Stacks

SwiftUI:

VStack(alignment: .leading, spacing: 10) {
    Text("Title")
    Text("Subtitle")
}

HStack(spacing: 20) {
    Circle().fill(.blue)
    Circle().fill(.red)
}

ZStack(alignment: .topTrailing) {
    Rectangle().fill(.gray)
    Text("Badge")
}

VML:

<VStack alignment="leading" spacing="10">
  <Text>Title</Text>
  <Text>Subtitle</Text>
</VStack>

<HStack spacing="20">
  <Circle style="fill(.blue)"/>
  <Circle style="fill(.red)"/>
</HStack>

<ZStack alignment="topTrailing">
  <Rectangle style="fill(.gray)"/>
  <Text>Badge</Text>
</ZStack>

10.3.2 ScrollView

SwiftUI:

ScrollView(.horizontal, showsIndicators: false) {
    HStack {
        ForEach(items) { item in
            Text(item.name)
        }
    }
}

VML (static content only):

<ScrollView axis="horizontal" showsIndicators="false">
  <HStack>
    <Text>Item 1</Text>
    <Text>Item 2</Text>
    <Text>Item 3</Text>
  </HStack>
</ScrollView>

10.3.3 List

SwiftUI:

List {
    Text("Row 1")
        .listRowBackground(Color.blue)
    Text("Row 2")
        .listRowSeparator(.hidden)
    HStack {
        Text("Row 3")
    }
    .listRowInsets(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20))
}

VML:

<List>
  <Text style="listRowBackground(.blue)">Row 1</Text>
  <Text style="listRowSeparator(.hidden)">Row 2</Text>
  <HStack style="listRowInsets(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20))">
    <Text>Row 3</Text>
  </HStack>
</List>

10.4 Shapes

SwiftUI:

Circle()
    .fill(.blue)
    .frame(width: 100, height: 100)

RoundedRectangle(cornerRadius: 15)
    .strokeBorder(.green, lineWidth: 2)
    
Rectangle()
    .fill(
        .linearGradient(
            colors: [.blue, .purple],
            startPoint: .topLeading,
            endPoint: .bottomTrailing
        )
    )

VML:

<Circle style="fill(.blue), frame(width: 100, height: 100)"/>

<RoundedRectangle cornerRadius="15" style="strokeBorder(.green, lineWidth: 2)"/>

<Rectangle style="fill(.linearGradient(colors: [.blue, .purple], startPoint: .topLeading, endPoint: .bottomTrailing))"/>

10.5 Controls

10.5.1 Button

SwiftUI:

Button("Save") {
    // Action
}
.buttonStyle(.borderedProminent)

Button(action: {}) {
    HStack {
        Image(systemName: "plus")
        Text("Add")
    }
}

VML:

<Button style="buttonStyle(.borderedProminent)">Save</Button>

<Button>
  <HStack>
    <Image systemName="plus"/>
    <Text>Add</Text>
  </HStack>
</Button>

10.5.2 TextField

SwiftUI:

TextField("Email", text: $email)
    .textFieldStyle(.roundedBorder)
    .keyboardType(.emailAddress)

VML:

<TextField style="textFieldStyle(.roundedBorder), keyboardType(.emailAddress)">Email</TextField>

10.5.3 Toggle

SwiftUI:

Toggle("Notifications", isOn: $notificationsEnabled)
Toggle(isOn: .constant(true)) {
    Label("WiFi", systemImage: "wifi")
}

VML:

<Toggle>Notifications</Toggle>
<Toggle isOn="true">
  <Label systemImage="wifi">WiFi</Label>
</Toggle>

10.6 Navigation

10.6.1 NavigationStack

SwiftUI:

NavigationStack {
    List {
        NavigationLink("Settings", destination: SettingsView())
        NavigationLink(destination: ProfileView()) {
            Label("Profile", systemImage: "person")
        }
    }
    .navigationTitle("Home")
}

VML:

<NavigationStack>
  <List style="navigationTitle('Home')">
    <NavigationLink>
      <Text template="label">Settings</Text>
      <SettingsView template="destination"/>
    </NavigationLink>
    <NavigationLink>
      <Label systemImage="person" template="label">Profile</Label>
      <ProfileView template="destination"/>
    </NavigationLink>
  </List>
</NavigationStack>

10.7 Container Views

10.7.1 Form and Section

SwiftUI:

Form {
    Section("Personal Information") {
        TextField("Name", text: $name)
        TextField("Email", text: $email)
    }
    
    Section {
        Toggle("Notifications", isOn: $notifications)
    } header: {
        Text("Preferences")
    } footer: {
        Text("Choose your notification preferences")
    }
}

VML:

<Form>
  <Section>
    <Text template="header">Personal Information</Text>
    <TextField>Name</TextField>
    <TextField>Email</TextField>
  </Section>
  
  <Section>
    <Text template="header">Preferences</Text>
    <Text template="footer">Choose your notification preferences</Text>
    <Toggle>Notifications</Toggle>
  </Section>
</Form>

10.7.2 TabView

SwiftUI:

TabView {
    HomeView()
        .tabItem {
            Label("Home", systemImage: "house")
        }
    
    SettingsView()
        .tabItem {
            Label("Settings", systemImage: "gear")
        }
}

VML:

<TabView>
  <HomeView style="tabItem(:home)">
    <Label systemImage="house" template="home">Home</Label>
  </HomeView>
  
  <SettingsView style="tabItem(:settings)">
    <Label systemImage="gear" template="settings">Settings</Label>
  </SettingsView>
</TabView>

10.7.3 DisclosureGroup

SwiftUI:

DisclosureGroup("Advanced Options") {
    Toggle("Option 1", isOn: $option1)
    Toggle("Option 2", isOn: $option2)
}

VML:

<DisclosureGroup>
  <Text template="label">Advanced Options</Text>
  <Toggle>Option 1</Toggle>
  <Toggle>Option 2</Toggle>
</DisclosureGroup>

10.7.4 Menu

SwiftUI:

Menu("Actions") {
    Button("Edit") { }
    Button("Delete") { }
    Divider()
    Button("Share") { }
}

VML:

<Menu>
  <Text template="label">Actions</Text>
  <Button>Edit</Button>
  <Button>Delete</Button>
  <Divider/>
  <Button>Share</Button>
</Menu>

10.8 Utility Views

10.8.1 ViewThatFits

SwiftUI:

ViewThatFits {
    Text("This is a very long text that might not fit")
    Text("Shorter text")
    Text("Tiny")
}

VML:

<ViewThatFits>
  <Text>This is a very long text that might not fit</Text>
  <Text>Shorter text</Text>
  <Text>Tiny</Text>
</ViewThatFits>

10.8.2 Color

SwiftUI:

Color.blue
Color(red: 0.5, green: 0.2, blue: 0.8)
Color("CustomColor")
Color(UIColor.systemRed)

VML:

<Color name="system-blue"/>
<Color red="0.5" green="0.2" blue="0.8"/>
<Color name="CustomColor"/>
<Color name="system-red"/>
<Color name="#ff0000"/>

10.8.3 Spacer

SwiftUI:

HStack {
    Text("Left")
    Spacer()
    Text("Right")
}

Spacer()
    .frame(height: 20)

VML:

<HStack>
  <Text>Left</Text>
  <Spacer/>
  <Text>Right</Text>
</HStack>

<Spacer style="frame(height: 20)"/>

10.8.4 Divider

SwiftUI:

VStack {
    Text("Above")
    Divider()
    Text("Below")
}

VML:

<VStack>
  <Text>Above</Text>
  <Divider/>
  <Text>Below</Text>
</VStack>

10.9 Progress Indicators

SwiftUI:

ProgressView()
ProgressView("Loading...")
ProgressView(value: 0.7)

VML:

<ProgressView/>
<ProgressView>Loading...</ProgressView>
<ProgressView value="0.7"/>

10.10 Links

SwiftUI:

Link("Visit Apple", destination: URL(string: "https://apple.com")!)

ShareLink(item: URL(string: "https://example.com")!) {
    Label("Share", systemImage: "square.and.arrow.up")
}

VML:

<Link destination="https://apple.com">Visit Apple</Link>

<ShareLink item="https://example.com">
  <Label systemImage="square.and.arrow.up">Share</Label>
</ShareLink>

10.11 Gauge

SwiftUI:

Gauge(value: 0.7, in: 0...1) {
    Text("Battery")
} currentValueLabel: {
    Text("70%")
} minimumValueLabel: {
    Text("0")
} maximumValueLabel: {
    Text("100")
}

VML:

<Gauge value="0.7" in="0...1">
  <Text template="label">Battery</Text>
  <Text template="currentValueLabel">70%</Text>
  <Text template="minimumValueLabel">0</Text>
  <Text template="maximumValueLabel">100</Text>
</Gauge>

11. Modifiers Reference

11.1 Layout Modifiers

SwiftUI Modifier VML Style Syntax
.padding() padding()
.padding(.all, 10) padding(.all, 10)
.padding(.horizontal, 20) padding(.horizontal, 20)
.frame(width: 100) frame(width: 100)
.frame(width: 100, height: 50) frame(width: 100, height: 50)
.frame(maxWidth: .infinity) frame(maxWidth: .infinity)
.offset(x: 10, y: 20) offset(x: 10, y: 20)
.position(x: 100, y: 200) position(x: 100, y: 200)

11.2 Appearance Modifiers

SwiftUI Modifier VML Style Syntax
.foregroundStyle(.blue) foregroundStyle(.blue)
.background(.yellow) background(.yellow)
.opacity(0.5) opacity(0.5)
.blur(radius: 3) blur(radius: 3)
.shadow(radius: 5) shadow(radius: 5)
.shadow(color: .black.opacity(0.3), radius: 5, x: 0, y: 2) shadow(color: .black.opacity(0.3), radius: 5, x: 0, y: 2)

11.3 Shape Modifiers

SwiftUI Modifier VML Style Syntax
.clipShape(Circle()) clipShape(.circle)
.clipShape(RoundedRectangle(cornerRadius: 10)) clipShape(.rect(cornerRadius: 10))
.cornerRadius(8) Deprecated - use clipShape(.rect(cornerRadius: 8))

11.4 Transform Modifiers

SwiftUI Modifier VML Style Syntax
.rotationEffect(.degrees(45)) rotationEffect(.degrees(45))
.scaleEffect(1.5) scaleEffect(1.5)
.scaleEffect(x: 2, y: 0.5) scaleEffect(x: 2, y: 0.5)

11.5 Text Modifiers

SwiftUI Modifier VML Style Syntax
.font(.title) font(.title)
.font(.system(size: 18)) font(.system(size: 18))
.fontWeight(.bold) fontWeight(.bold)
.bold() bold()
.italic() italic()
.textCase(.uppercase) textCase(.uppercase)
.lineLimit(2) lineLimit(2)
.multilineTextAlignment(.center) multilineTextAlignment(.center)

11.6 Presentation Modifiers

SwiftUI Modifier VML Style Syntax
.sheet(isPresented: $show) sheet(isPresented: attr(show), content: :content)
.fullScreenCover(isPresented: $show) fullScreenCover(isPresented: attr(show), content: :content)
.alert("Title", isPresented: $show) alert("Title", isPresented: attr(show), actions: :actions, message: :message)
.confirmationDialog("Title", isPresented: $show) confirmationDialog("Title", isPresented: attr(show), actions: :actions, message: :message)
.popover(isPresented: $show) popover(isPresented: attr(show), content: :content)

11.7 Environment Modifiers

SwiftUI Modifier VML Style Syntax
.environment(\.colorScheme, .dark) environment(\.colorScheme, .dark)
.environment(\.locale, Locale(identifier: "fr")) environment(\.locale, Locale(identifier: "fr"))

11.8 Animation Modifiers

SwiftUI Modifier VML Style Syntax
.animation(.easeInOut) animation(.easeInOut)
.animation(.spring(), value: size) animation(.spring(), value: true)
.transition(.slide) transition(.slide)
.transition(.opacity.combined(with: .scale)) transition(.opacity.combined(with: .scale))

11.9 ViewBuilder Modifiers

Modifiers that accept ViewBuilder content use template slots:

SwiftUI:

Text("Content")
    .overlay(alignment: .topTrailing) {
        Badge()
    }
    .background {
        RoundedRectangle(cornerRadius: 10)
            .fill(.blue)
    }

VML:

<Text style="overlay(alignment: .topTrailing, content: :overlay), background(content: :bg)">Content
  <Badge template="overlay"/>
  <RoundedRectangle cornerRadius="10" template="bg" style="fill(.blue)"/>
</Text>

12. Custom Extensions

12.1 Custom Views

Clients MAY support custom view types registered at runtime:

SwiftUI:

struct ProfileCard: View {
    let name: String
    let role: String
    
    var body: some View {
        VStack {
            Image(systemName: "person.circle")
            Text(name)
            Text(role)
        }
    }
}

// Usage
ProfileCard(name: "Jane Doe", role: "Designer")

VML:

<ProfileCard name="Jane Doe" role="Designer"/>

12.2 Custom Modifiers

Clients MAY support custom ViewModifiers registered at runtime:

SwiftUI:

struct CardStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.white)
            .clipShape(RoundedRectangle(cornerRadius: 10))
            .shadow(radius: 5)
    }
}

// Usage
Text("Card").modifier(CardStyle())

VML:

<Text style="cardStyle()">Card</Text>

12.3 Unknown Element and Modifier Handling

When a client encounters unknown elements or modifiers:

12.3.1 Unknown Elements

  • Client MUST log a warning when encountering an unknown element
  • Unknown elements MUST be rendered as a Group container
  • All valid modifiers on the unknown element MUST be applied to the Group
  • Child content MUST be rendered normally within the Group

12.3.2 Unknown Modifiers

  • Client MUST log a warning when encountering an unknown modifier
  • Unknown modifier MUST be skipped
  • Processing MUST continue with remaining modifiers in the style attribute

13. Grammar Definition

13.1 Lexical Grammar

letter        = "A"..."Z" | "a"..."z" | "_" ;
digit         = "0"..."9" ;
hexdigit      = digit | "A"..."F" | "a"..."f" ;

IDENT         = letter , { letter | digit } ;
SYMBOL        = ":" , IDENT ;
INT           = digit , { digit } ;
FLOAT         = INT , "." , { digit } | "." , digit , { digit } ;
NUMBER        = FLOAT | INT ;
ANGLE_DEG     = NUMBER , "deg" ;
STRING        = '"' , { char } , '"' ;

(* Color formats *)
CSSHEX        = "#" , hexdigit , hexdigit , hexdigit , hexdigit , hexdigit , hexdigit , 
                [ hexdigit , hexdigit ] ;
RGBFUNC       = "rgb(" , NUMBER , "," , NUMBER , "," , NUMBER , ")" ;
HSLFUNC       = "hsl(" , NUMBER , "," , NUMBER , "%," , NUMBER , "%)" ;
CSSCOLOR      = CSSHEX | RGBFUNC | HSLFUNC ;

(* Key path *)
KEYPATH       = "\" , "." , IDENT , { "." , IDENT } ;

(* Enum/member access *)
MEMBER_CALL   = "." , IDENT , [ "(" , [ ArgList ] , ")" ] ;
MEMBER_CHAIN  = MEMBER_CALL , { MEMBER_CALL } ;

13.2 Style Grammar

Style         = Modifier , { ";" , Modifier } ;
Modifier      = IDENT , "(" , [ ArgList ] , ")" ;
ArgList       = Arg , { "," , SP , Arg } ;
Arg           = [ Label , ":" , SP ] , Expr ;
Label         = IDENT ;

(* Expression precedence: SYMBOL has highest priority *)
Expr          = SYMBOL
              | NUMBER 
              | ANGLE_DEG 
              | STRING 
              | CSSCOLOR 
              | MEMBER_CHAIN
              | FUNC_CALL 
              | Array 
              | AttrCall
              | KEYPATH ;

Array         = "[" , [ Expr , { "," , SP , Expr } ] , "]" ;
FUNC_CALL     = IDENT , "(" , [ ArgList ] , ")" ;

AttrCall      = "attr" , "(" , AttrInner , ")" ;
AttrInner     = IDENT , [ SP , TypeHint ] , [ "," , SP , Fallback ] ;
TypeHint      = "type(" , "<" , AttrType , ">" , ")" ;
AttrType      = "string" | "number" | "integer" | "length" 
              | "angle" | "color" | "url" | "boolean" ;
Fallback      = Expr ;

SP            = " " ;

13.3 Document Grammar

Document      = "<!doctype swiftui+vml>" , VMLElement ;
VMLElement    = "<vml>" , Head? , Body , "</vml>" ;
Head          = "<head>" , HeadContent* , "</head>" ;
Body          = "<body>" , ViewContent , "</body>" ;

HeadContent   = StyleLink | TemplateView ;
StyleLink     = "<Style" , Attributes , "/>" ;
TemplateView  = ViewElement ;

ViewContent   = ViewElement | TextContent | ViewContent* ;
ViewElement   = "<" , ElementName , Attributes? , 
                ( "/>" | ">" , ViewContent , "</" , ElementName , ">" ) ;
TextContent   = { char } ;

Attributes    = { SP , AttributeName , '="' , AttributeValue , '"' } ;
ElementName   = IDENT ;
AttributeName = IDENT ;
AttributeValue = { char } ;  (* With proper entity escaping *)

14. Examples

14.1 Complete Login Form

SwiftUI:

struct LoginView: View {
    @State private var email = ""
    @State private var password = ""
    @State private var rememberMe = false
    @State private var showingAlert = false
    
    var body: some View {
        NavigationStack {
            VStack(spacing: 20) {
                Image(systemName: "lock.circle.fill")
                    .resizable()
                    .frame(width: 80, height: 80)
                    .foregroundStyle(.blue)
                
                VStack(spacing: 16) {
                    TextField("Email", text: $email)
                        .textFieldStyle(.roundedBorder)
                        .keyboardType(.emailAddress)
                        .textContentType(.emailAddress)
                    
                    SecureField("Password", text: $password)
                        .textFieldStyle(.roundedBorder)
                        .textContentType(.password)
                    
                    Toggle("Remember me", isOn: $rememberMe)
                }
                .padding(.horizontal)
                
                Button(action: { }) {
                    Text("Sign In")
                        .frame(maxWidth: .infinity)
                }
                .buttonStyle(.borderedProminent)
                .padding(.horizontal)
                
                HStack {
                    Link("Forgot Password?", destination: URL(string: "https://example.com/forgot")!)
                        .font(.footnote)
                    Spacer()
                    Link("Sign Up", destination: URL(string: "https://example.com/signup")!)
                        .font(.footnote)
                }
                .padding(.horizontal)
                
                Spacer()
            }
            .padding(.top, 50)
            .navigationTitle("Login")
            .alert("Error", isPresented: $showingAlert) {
                Button("OK") { }
            } message: {
                Text("Invalid credentials")
            }
        }
    }
}

VML:

<!doctype swiftui+vml>
<vml>
  <body>
    <NavigationStack>
      <VStack spacing="20" style="padding(.top, 50), navigationTitle('Login'), alert('Error', isPresented: attr(showAlert), actions: :alertActions, message: :alertMessage)" showAlert="false">
        <Image systemName="lock.circle.fill" style="resizable(), frame(width: 80, height: 80), foregroundStyle(.blue)"/>
        
        <VStack spacing="16" style="padding(.horizontal)">
          <TextField style="textFieldStyle(.roundedBorder), keyboardType(.emailAddress), textContentType(.emailAddress)">Email</TextField>
          
          <SecureField style="textFieldStyle(.roundedBorder), textContentType(.password)">Password</SecureField>
          
          <Toggle>Remember me</Toggle>
        </VStack>
        
        <Button style="buttonStyle(.borderedProminent), padding(.horizontal)">
          <Text style="frame(maxWidth: .infinity)">Sign In</Text>
        </Button>
        
        <HStack style="padding(.horizontal)">
          <Link destination="https://example.com/forgot" style="font(.footnote)">Forgot Password?</Link>
          <Spacer/>
          <Link destination="https://example.com/signup" style="font(.footnote)">Sign Up</Link>
        </HStack>
        
        <Spacer/>
        
        <!-- Alert templates -->
        <Group template="alertActions">
          <Button>OK</Button>
        </Group>
        <Text template="alertMessage">Invalid credentials</Text>
      </VStack>
    </NavigationStack>
  </body>
</vml>

14.2 Product Card with Overlay

SwiftUI:

struct ProductCard: View {
    let product: Product
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            ZStack(alignment: .topTrailing) {
                Image(product.imageName)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(height: 200)
                    .clipped()
                
                if product.isOnSale {
                    Text("SALE")
                        .font(.caption)
                        .fontWeight(.bold)
                        .padding(.horizontal, 8)
                        .padding(.vertical, 4)
                        .background(.red)
                        .foregroundStyle(.white)
                        .clipShape(Capsule())
                        .padding(8)
                }
            }
            
            Text(product.name)
                .font(.headline)
                .lineLimit(1)
            
            Text(product.description)
                .font(.subheadline)
                .foregroundStyle(.secondary)
                .lineLimit(2)
            
            HStack {
                Text("$\(product.price, specifier: "%.2f")")
                    .font(.title3)
                    .fontWeight(.bold)
                
                if let originalPrice = product.originalPrice {
                    Text("$\(originalPrice, specifier: "%.2f")")
                        .font(.caption)
                        .strikethrough()
                        .foregroundStyle(.secondary)
                }
                
                Spacer()
                
                Button(action: {}) {
                    Image(systemName: "cart.badge.plus")
                }
                .buttonStyle(.bordered)
            }
        }
        .padding()
        .background(.background)
        .clipShape(RoundedRectangle(cornerRadius: 12))
        .shadow(radius: 4)
    }
}

VML (for a specific product instance):

<VStack alignment="leading" spacing="8" style="padding(), background(.background), clipShape(.rect(cornerRadius: 12)), shadow(radius: 4)">
  <ZStack alignment="topTrailing">
    <Image name="iphone-15" style="resizable(), aspectRatio(contentMode: .fill), frame(height: 200), clipped()"/>
    
    <Text style="font(.caption), fontWeight(.bold), padding(.horizontal, 8), padding(.vertical, 4), background(.red), foregroundStyle(.white), clipShape(.capsule), padding(8)">SALE</Text>
  </ZStack>
  
  <Text style="font(.headline), lineLimit(1)">iPhone 15 Pro</Text>
  
  <Text style="font(.subheadline), foregroundStyle(.secondary), lineLimit(2)">The most advanced iPhone ever with titanium design</Text>
  
  <HStack>
    <Text style="font(.title3), fontWeight(.bold)">$999.00</Text>
    
    <Text style="font(.caption), strikethrough(), foregroundStyle(.secondary)">$1099.00</Text>
    
    <Spacer/>
    
    <Button style="buttonStyle(.bordered)">
      <Image systemName="cart.badge.plus"/>
    </Button>
  </HStack>
</VStack>

14.3 Dashboard with Gradient Background

SwiftUI:

struct DashboardView: View {
    var body: some View {
        ZStack {
            // Gradient background
            LinearGradient(
                colors: [.blue.opacity(0.3), .purple.opacity(0.3)],
                startPoint: .topLeading,
                endPoint: .bottomTrailing
            )
            .ignoresSafeArea()
            
            ScrollView {
                VStack(spacing: 20) {
                    // Header
                    HStack {
                        VStack(alignment: .leading) {
                            Text("Welcome back")
                                .font(.subheadline)
                                .foregroundStyle(.secondary)
                            Text("Dashboard")
                                .font(.largeTitle)
                                .fontWeight(.bold)
                        }
                        Spacer()
                        Image(systemName: "person.circle.fill")
                            .resizable()
                            .frame(width: 44, height: 44)
                            .foregroundStyle(.gray)
                    }
                    .padding()
                    
                    // Stats Cards
                    HStack(spacing: 16) {
                        StatCard(
                            title: "Revenue",
                            value: "$12,543",
                            change: "+12%",
                            isPositive: true
                        )
                        
                        StatCard(
                            title: "Orders",
                            value: "432",
                            change: "-3%",
                            isPositive: false
                        )
                    }
                    .padding(.horizontal)
                    
                    // Chart placeholder
                    RoundedRectangle(cornerRadius: 20)
                        .fill(.ultraThinMaterial)
                        .frame(height: 200)
                        .overlay(
                            Text("Chart")
                                .foregroundStyle(.secondary)
                        )
                        .padding(.horizontal)
                }
            }
        }
    }
}

struct StatCard: View {
    let title: String
    let value: String
    let change: String
    let isPositive: Bool
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(title)
                .font(.caption)
                .foregroundStyle(.secondary)
            
            Text(value)
                .font(.title2)
                .fontWeight(.bold)
            
            HStack(spacing: 4) {
                Image(systemName: isPositive ? "arrow.up.right" : "arrow.down.right")
                    .font(.caption)
                Text(change)
                    .font(.caption)
            }
            .foregroundStyle(isPositive ? .green : .red)
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding()
        .background(.ultraThinMaterial)
        .clipShape(RoundedRectangle(cornerRadius: 12))
    }
}

VML:

<ZStack>
  <!-- Gradient background -->
  <Rectangle style="fill(.linearGradient(colors: [.blue.opacity(0.3), .purple.opacity(0.3)], startPoint: .topLeading, endPoint: .bottomTrailing)), ignoresSafeArea()"/>
  
  <ScrollView>
    <VStack spacing="20">
      <!-- Header -->
      <HStack style="padding()">
        <VStack alignment="leading">
          <Text style="font(.subheadline), foregroundStyle(.secondary)">Welcome back</Text>
          <Text style="font(.largeTitle), fontWeight(.bold)">Dashboard</Text>
        </VStack>
        <Spacer/>
        <Image systemName="person.circle.fill" style="resizable(), frame(width: 44, height: 44), foregroundStyle(.gray)"/>
      </HStack>
      
      <!-- Stats Cards -->
      <HStack spacing="16" style="padding(.horizontal)">
        <!-- Revenue Card -->
        <VStack alignment="leading" spacing="8" style="frame(maxWidth: .infinity, alignment: .leading), padding(), background(.ultraThinMaterial), clipShape(.rect(cornerRadius: 12))">
          <Text style="font(.caption), foregroundStyle(.secondary)">Revenue</Text>
          <Text style="font(.title2), fontWeight(.bold)">$12,543</Text>
          <HStack spacing="4" style="foregroundStyle(.green)">
            <Image systemName="arrow.up.right" style="font(.caption)"/>
            <Text style="font(.caption)">+12%</Text>
          </HStack>
        </VStack>
        
        <!-- Orders Card -->
        <VStack alignment="leading" spacing="8" style="frame(maxWidth: .infinity, alignment: .leading), padding(), background(.ultraThinMaterial), clipShape(.rect(cornerRadius: 12))">
          <Text style="font(.caption), foregroundStyle(.secondary)">Orders</Text>
          <Text style="font(.title2), fontWeight(.bold)">432</Text>
          <HStack spacing="4" style="foregroundStyle(.red)">
            <Image systemName="arrow.down.right" style="font(.caption)"/>
            <Text style="font(.caption)">-3%</Text>
          </HStack>
        </VStack>
      </HStack>
      
      <!-- Chart placeholder -->
      <RoundedRectangle cornerRadius="20" style="fill(.ultraThinMaterial), frame(height: 200), overlay(content: :chartLabel), padding(.horizontal)">
        <Text template="chartLabel" style="foregroundStyle(.secondary)">Chart</Text>
      </RoundedRectangle>
    </VStack>
  </ScrollView>
</ZStack>

Appendix A: Platform Considerations

A.1 iOS Target

When target is iOS:

  • Default touch target minimum: 44pt
  • Safe area insets apply
  • Navigation bars use iOS styling
  • Keyboard types are functional

A.2 macOS Target

When target is macOS:

  • Mouse hover states may apply
  • Window chrome considerations
  • Menu bar integration possible
  • Different control styles

A.3 visionOS Target

When target is visionOS:

  • 3D transforms may be available
  • Depth and layering considerations
  • Gesture requirements differ
  • Spatial layout rules apply

Appendix B: Error Handling

B.1 Parser Errors

VML parsers MUST handle:

  • Malformed XML structure
  • Unrecognized elements (log warning, skip)
  • Invalid attribute values (use defaults)
  • Missing required attributes (use defaults or skip)

B.2 Runtime Errors

Clients SHOULD handle:

  • Missing template references (log warning)
  • attr() resolution failures (use fallback)
  • Circular template references (break cycle)
  • Invalid modifier syntax (skip modifier)

B.3 Document Validation and Error Recovery

When VML documents violate specification rules:

  • Clients SHOULD attempt to parse and render what is possible, similar to HTML browser behavior
  • VML parsing operates in strict mode by default
  • Rule violations may cause parsing failures or improper SwiftUI rendering
  • Clients SHOULD render all parseable content even when encountering malformed sections

Appendix C: Experimental Features

C.1 GeometryReader (Experimental)

GeometryReader support is marked as experimental. When supported:

<GeometryReader>
  <Text style="frame(width: geometry.size.width / 2)">Half Width</Text>
</GeometryReader>

C.1.1 GeometryReader Behavior

  • The client creates a geometry object when GeometryReader is present
  • Geometry values are available to descendant views in their style attributes only
  • Geometry values are NOT accessible in view content or text
  • When GeometryReaders are nested, the inner GeometryReader replaces the scope of the outer one (current limitation due to experimental nature)

The geometry object and its properties are handled entirely by the client. No template interpolation occurs in VML markup.

Appendix D: Future Considerations

The following features may be considered for future versions:

  • Conditional rendering directives
  • Limited expression evaluation
  • Animation keyframes
  • Gesture recognizer hints
  • Accessibility annotations
  • Localization markers

End of Specification

This specification provides a complete and authoritative definition of the View Markup Language (VML) for converting SwiftUI to a static, declarative markup format suitable for server-side generation and client-side rendering.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment