Skip to content

Instantly share code, notes, and snippets.

@bcardarella
Last active March 21, 2025 18:50
Show Gist options
  • Save bcardarella/d9c0cd1f4da424f154e8fe8cc2d88723 to your computer and use it in GitHub Desktop.
Save bcardarella/d9c0cd1f4da424f154e8fe8cc2d88723 to your computer and use it in GitHub Desktop.

Native Markup Language (NML) Specification

Version 1.0

1. Overview

Native Markup Language (NML) is a markup language derived from SGML, designed to represent composable UI frameworks. This specification defines the conversion from SwiftUI to NML.

2. Document Headers and Type

2.1 HTTP Headers

When serving NML documents:

Request Headers:

ACCEPT: application/swiftui

Response Headers:

CONTENT-TYPE: application/swiftui

2.2 Document Type Declaration

NML documents must begin with a doctype declaration. For SwiftUI documents:

<!doctype swiftui>
<nml>
    <head>
        <Style url="/styles/main.nss"/>
    </head>
    <body>
        <Text>Hello World</Text>
    </body>
</nml>

The doctype declaration is used for document annotation only and is ignored by parsers.

3. Document Structure

3.1 Basic Structure

NML documents require the following structure:

<nml>
    <head>
        <Style url="/styles/main.nss"/>
    </head>
    <body>
        <!-- Application views go here -->
        <NavigationStack>
            <Text>Hello World</Text>
        </NavigationStack>
    </body>
</nml>

3.2 Head Section

The head section contains:

  • Style references using the Style tag
  • Template definitions for lifecycle views
  • Other client configuration elements

Example of style inclusion:

<head>
    <Style url="/base.nss"/>
    <Style url="/components.nss"/>
    <Style url="/theme-dark.nss"/>
</head>

3.3 Body Section

The body tag is required and must contain the application's view hierarchy:

<body>
    <TabView>
        <VStack tabItem="Home">
            <Text>Welcome to the Home Tab</Text>
            <Button>Get Started</Button>
        </VStack>
        
        <ScrollView tabItem="Settings">
            <VStack spacing="16">
                <Toggle isOn="true">Dark Mode</Toggle>
                <Toggle isOn="false">Notifications</Toggle>
            </VStack>
        </ScrollView>
    </TabView>
</body>

3.4 Lifecycle Templates

Clients can define views for different UI lifecycle states in the head section:

SwiftUI Lifecycle States

Disconnected State
<head>
    <VStack template="disconnected" spacing="16">
        <Image source="cloud.slash"/>
        <Text style="font(.headline)">Connection Lost</Text>
        <Text style="foregroundStyle(.secondary)">
            Attempting to reconnect...
        </Text>
        <ProgressView/>
    </VStack>
</head>
Reconnecting State
<head>
    <VStack template="reconnecting" spacing="16">
        <ProgressView style="progressViewStyle(.circular)"/>
        <Text>Reconnecting...</Text>
        <Button style="buttonStyle(.bordered)">
            Cancel
        </Button>
    </VStack>
</head>
Error State
<head>
    <VStack template="error" spacing="16">
        <Image source="exclamationmark.triangle.fill"/>
        <Text style="font(.headline)">An Error Occurred</Text>
        <Text style="foregroundStyle(.secondary)">
            Unable to complete the requested operation
        </Text>
        <Button style="buttonStyle(.bordered)">
            Retry
        </Button>
    </VStack>
</head>

4. Basic Conversion Rules

4.1 Views to Elements

SwiftUI views convert directly to NML elements with exact case preservation of the view name:

LazyVStack(alignment: .leading) {
    Text("Hello")
    ScrollView(.horizontal) {
        Text("World")
    }
}

Converts to:

<LazyVStack alignment="leading">
    <Text>Hello</Text>
    <ScrollView axis="horizontal">
        <Text>World</Text>
    </ScrollView>
</LazyVStack>

4.2 Arguments to Attributes

View initialization arguments become element attributes with exact case preservation:

NavigationLink(destination: DetailView(), isActive: true, label: "Settings")
    .buttonStyle(.bordered)

Converts to:

<NavigationLink destination="DetailView" isActive="true" label="Settings">

The values maintain their string representation, but the client infers:

  • isActive="true" as Bool because isActive is Bool in SwiftUI
  • label="Settings" as String because label is String in SwiftUI
  • destination="DetailView" as View because destination is View in SwiftUI

4.3 Boolean Attributes

Unlike HTML, NML requires explicit boolean values for attributes:

Toggle("Light", isOn: true)
Toggle("Dark", isOn: false)

Converts to:

<Toggle isOn="true">Light</Toggle>
<Toggle isOn="false">Dark</Toggle>

The following are invalid in NML:

<!-- Invalid: boolean attribute without value -->
<Toggle isOn>Light</Toggle>

<!-- Invalid: omitted boolean attribute -->
<Toggle>Light</Toggle>

4.4 Attribute Value Encoding

  • All attribute values are strings
  • Special characters must be encoded using HTML character entities:
    • " becomes &quot;
    • & becomes &amp;
    • < becomes &lt;
    • > becomes &gt;

JSON encoding is only allowed for arrays and lists in attribute values:

Text(items: [1, 2, 3])

Converts to:

<Text items="[1,2,3]"/>

4.5 The id Modifier

The id modifier is unique in that it's the only modifier that converts to an attribute rather than being included in the style attribute:

Text("Hello")
    .id("greeting")
    .padding()

Converts to:

<Text id="greeting" style="padding()">Hello</Text>

4.6 Modifiers to Style Attributes

SwiftUI view modifiers convert to style attributes, with member expressions retaining their leading dots:

Text("Hello")
    .padding()
    .foregroundStyle(.green)

Converts to:

<Text style="padding(); foregroundStyle(.green)">Hello</Text>

4.7 View Builder Closures and Templates

View builder closures in modifiers use template references. Templates must be defined as direct children of the element that references them, and template names must only be unique among siblings:

VStack {
    RoundedRectangle(cornerRadius: 8)
        .overlay(alignment: .topLeading) {
            Star(color: .red)
        }
    
    Circle()
        .overlay {
            Star(color: .blue)
        }
}

Converts to:

<VStack>
    <RoundedRectangle
        cornerRadius="8"
        style="overlay(alignment: .topLeading, content: :starOverlay)"
    >
        <Star color="red" template="starOverlay"/>
    </RoundedRectangle>
    
    <Circle style="overlay(content: :starOverlay)">
        <Star color="blue" template="starOverlay"/>
    </Circle>
</VStack>

5. The attr Helper

The attr helper provides a way to reference element attribute values within style declarations:

VStack {
}
.navigationTitle("Home")

Converts to:

<VStack navTitle="Home" style="navigationTitle(attr(:navTitle))">

Multiple attr references can be used within the same style string:

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

Converts to:

<Circle
    fillColor="blue"
    frameWidth="100"
    frameHeight="100"
    padAmount="20"
    style="fill(attr(:fillColor)); frame(width: attr(:frameWidth), height: attr(:frameHeight)); padding(.horizontal, attr(:padAmount))"
/>

6. Stylesheets

Similar to HTML, NML supports extracting styles into separate stylesheets that can be referenced using class names:

Button("Submit") {
}
.padding()
.background(.blue)
.foregroundStyle(.white)

Can be represented using a class reference:

<Button class="primary-button">Submit</Button>

The definition and structure of stylesheets are defined in the Native Stylesheet (NSS) specification document.

7. SwiftUI Documentation Conversion

7.1 Initialize with Arguments

SwiftUI:

/// Creates a toggle that generates its label from a string.
///
/// - Parameters:
///   - title: A string that describes the purpose of the toggle.
///   - isOn: A binding to a Boolean value that determines the toggle's state.
public init(_ title: String, isOn: Binding<Bool>)

SwiftUI usage:

Toggle("Switch Theme", isOn: $isDarkMode)

Converts to:

<Toggle isOn="true">Switch Theme</Toggle>

7.2 Multiple Initializer Variants

SwiftUI:

extension Button where Label == Text {
    /// Creates a button that displays a text label.
    public init(_ title: String, action: @escaping () -> Void)
    
    /// Creates a button that displays a localized text label.
    public init(_ titleKey: LocalizedStringKey, action: @escaping () -> Void)
}

SwiftUI usage:

Button("Save") { 
    saveData() 
}

Button("welcome.message") { 
    showWelcome() 
}

Converts to:

<Button>Save</Button>

<Button localized="true">welcome.message</Button>

8. Validation Rules

  1. Elements must be properly nested and closed
  2. Template names must be unique only among sibling elements
  3. Templates must be defined as direct children of the element referencing them
  4. All attribute values must be properly encoded
  5. Member expressions within style values must retain their leading dot (e.g., .green), while attribute values on elements must not have leading dots (e.g., alignment="leading")
  6. Only arrays and lists may be JSON-encoded in attribute values
  7. All attr references must point to existing attributes on the current element
  8. Boolean attributes must have explicit values
  9. Application views must be contained within the body tag
  10. Lifecycle templates must be defined in the head section

9. File Format

  • File extension: .nml
  • Encoding: UTF-8
  • Whitespace is preserved within text content
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment