Skip to content

Instantly share code, notes, and snippets.

@bcardarella
Created June 24, 2025 15:34
Show Gist options
  • Save bcardarella/80a66959529f23796480f0647573bd25 to your computer and use it in GitHub Desktop.
Save bcardarella/80a66959529f23796480f0647573bd25 to your computer and use it in GitHub Desktop.
VML - Jetpack Compose

View Markup Language (VML) Specification - Jetpack Compose

Version 1.0-alpha.1

1. Overview

View Markup Language (VML) is a markup language derived from SGML, designed to represent composable UI frameworks for unidirectional, server-driven rendering. This specification defines the conversion from a server-side view model to VML for use with Jetpack Compose.

Specification Note 1.1: Scope The VML specification is concerned exclusively with the structure and rendering of composables. It is a unidirectional standard (server-to-client). Mechanisms for handling user interactions, actions, and client-to-server state changes are explicitly outside the scope of this document.

2. Document Headers and Type

2.1 HTTP Headers

When serving VML documents for Jetpack Compose:

Request Headers:

ACCEPT: application/jetpack

Response Headers:

CONTENT-TYPE: application/jetpack

2.2 Document Type Declaration

VML documents for Compose must begin with a doctype declaration.

<!doctype jetpack>
<vml>
    <head>
        <Style url="/styles/main.vss"/>
    </head>
    <body>
        <Text>Hello, Compose!</Text>
    </body>
</vml>

3. Document Structure

3.1 Basic Structure

VML documents require <vml>, <head>, and <body> tags.

<vml>
    <head>
        <Style url="/styles/main.vss"/>
    </head>
    <body>
        <Column>
            <Text>Hello, Compose!</Text>
        </Column>
    </body>
</vml>

3.2 Head Section

The head section contains:

  • Style references using the <Style> tag.
  • Template definitions for lifecycle views.

3.3 Body Section

The <body> tag is required and must contain the application's primary composable hierarchy.

3.4 Lifecycle Templates

Composables for different UI lifecycle states can be defined in the <head> section using the template attribute.

Specification Note 3.4: Template Invocation The VML specification defines the syntax for declaring lifecycle templates. The mechanism for triggering the rendering of these templates is the responsibility of the client-side implementation. The template attribute serves as a well-known identifier for the client to look up and render when its internal state changes.

4. Basic Conversion Rules

4.1 Composable to Element & The Composable Registry

Jetpack Compose functions convert directly to VML elements with exact case preservation.

// Jetpack Compose Code
Column(horizontalAlignment = Alignment.CenterHorizontally) {
    Text("Hello")
    Row {
        Text("World")
    }
}

Converts to:

<Column horizontalAlignment="Alignment.CenterHorizontally">
    <Text>Hello</Text>
    <Row>
        <Text>World</Text>
    </Row>
</Column>

Specification Note 4.1: Client-Side Composable Registry A VML client implementation must maintain a registry that maps VML element tag names to initializable Composable functions. The application developer is responsible for populating this registry at startup.

4.2 Arguments to Attributes & Deserialization

Composable function parameters become element attributes. The VML attribute name must match the Kotlin parameter name.

Specification Note 4.2: Type-Aware Deserialization & Data Types The client is responsible for performing type-aware deserialization by introspecting the function signature of the registered Composable. The following string representations must be supported:

  • Dp / Sp: Values can be plain integers (e.g., padding="16", defaulting to dp) or have an explicit suffix ("16dp", "14sp").
  • Color: Values can be constant names ("Red") or hex strings ("#FFFF0000").
  • Enums / Static Objects: Values must use the full dot-qualified name ("Alignment.CenterHorizontally").

4.3 style Attribute for Modifiers

All Jetpack Compose Modifiers are converted into a single, semi-colon-delimited style attribute. The order of modifiers must be preserved.

// Jetpack Compose Code
Text(
    "Hello",
    modifier = Modifier.padding(16.dp).background(Color.Blue)
)

Converts to:

<Text style="padding(16dp); background(#FF0000FF)">Hello</Text>

Specification Note 4.3: Reserved modifier keyword The attribute name style is used for modifier chains to maintain consistency. The attribute name modifier is reserved by the VML client and must not be used.

4.4 Composable Lambdas and Templates

Composable lambda parameters are populated using the template system.

Default Content Slot: Child elements without a template or slot attribute fill the default content: @Composable () -> Unit parameter.

Named Content Slots: To populate multiple named slots (e.g., in Scaffold), the parent element uses attributes to reference templates defined on child elements.

// Jetpack Compose Code
Scaffold(
    topBar = { TopAppBar(title = { Text("App") }) },
    content = { LazyColumn() }
)

Converts to:

<Scaffold topBar=":myTopBar">
    <TopAppBar template="myTopBar" title="App" />
    <LazyColumn /> </Scaffold>

5. The attr Helper

The attr helper is used to create parameterized styles by referencing the value of another attribute on the same element. Its primary purpose is to allow a single style class from a stylesheet to be reused with dynamic data.

// Jetpack Compose Code
Column(modifier = Modifier.padding(paddingValue.dp))

Can be converted to VML as:

<Column paddingValue="16" style="padding(attr(:paddingValue))" />

6. Stylesheets

Styles can be extracted into separate stylesheets and referenced using class attributes.

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

Specification Note 6.1: Client-Specific Stylesheet Syntax The VML standard does not define the syntax for the content of stylesheet files. The syntax must be client-specific and consistent with the target native UI framework (e.g., Jetpack Compose Modifier syntax for an Android client). The rules of precedence between inline style attributes and class attributes are also an implementation detail of the client.

7. Jetpack Compose to VML Conversion Examples

This section provides further examples of how different Jetpack Compose function signatures are converted to VML.

7.1 Initializer with Standard Parameters

A standard composable with named parameters directly maps to attributes.

// Jetpack Compose Usage
Row(verticalAlignment = Alignment.CenterVertically) {
    Text(text = "Enable Notifications")
    Switch(checked = true, onCheckedChange = null)
}

Converts to:

<Row verticalAlignment="Alignment.CenterVertically">
    <Text>Enable Notifications</Text>
    <Switch checked="true" />
</Row>

7.2 Function Overloads

Different function overloads for a composable can be disambiguated by the presence of unique attribute names. The client's Composable Registry is responsible for selecting the correct overload based on the provided attributes.

// Hypothetical function overloads
@Composable fun Image(painter: Painter, ...)
@Composable fun Image(url: String, ...)

// VML Client Registration
registry.register("Image", ::Image(painter:))
registry.register("Image", ::Image(url:))

The VML can then clearly select an overload:

<Image painter="my_local_icon" />

<Image url="https://example.com/image.png" />

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. VML clients must implement a Composable Registry.
  6. The modifier attribute must not be used.
  7. Named content slots must be populated using the attribute-to-template reference system.

9. File Format

  • File extension: .vml
  • Encoding: UTF-8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment