core2-jvm (latest)
core2-jvm (latest)core-jvm
  • Home
  • Features
  • Spec
  • Guides
  • Sandbox
  • Step Studio
  • Overview
    • Alert
    • Display
    • Divider
    • Heading
    • Illustration
    • Image
    • Instructions
    • List
    • Loading indicator
    • Markdown
    • Paragraph
    • Progress
    • Review
    • Status list
    • Checkbox
    • Date
    • Date lookup
    • Integer
    • Money Input
    • Number
    • Password
    • Phone Number
    • Radio
    • Repeating items
    • Segmented
    • Select
    • Select (multiple)
    • Select (multiple, inline)
    • Select (multiple, inline, group)
    • Switch
    • Tabs
    • Text
    • Text area
    • Upload
    • Upload (multi-file)
    • Button
    • Decision
    • Decision (Filtered)
    • Search (async)
    • Align
    • Box
    • Columns
    • Margin
    • Modal
    • Section
    • Tabs
    • Autocomplete hint
    • Disabled
    • Form Section
    • Help
    • Model
    • Persisting data
    • Placeholder
    • Required
    • Avatar (URI)
    • Avatar (Text)
    • Image
    • Client-side validation
    • Server-side validation
    • Validation messages
    • Validation pattern
    • Analytics
    • Back navigation
    • Camera
    • External
    • Global error
    • Polling
    • Stack behaviour
    • Toolbar
    • Action
    • Refresh
    • Link
    • Copy
    • Modal
    • Dismiss
  • Success screens

Dynamic Flow features

This is a visual guide to the features that Dynamic Flow supports. Look in the top right of the code samples for links to copy and launch them in our interactive sandbox, where you can edit them further.

Content display

These components are used for the display of non-interactive content. In dynamic flow, we call these "layout" components.

Alert

Alert spec

Shows an attention-grabbing message. Bold and italic markdown are supported, but no links.

alert {
    markdown = "Exchange rates are unstable right now, so we can't guarantee this rate."
    context = Context.WARNING
}

Display

Heading spec

Display is a control on the heading component. It can be used for headlines, typically on a success screen.

heading {
    text = "Whoa, that was fast!"
    control = "display"
}

Divider

Divider spec

Divider is a visual asset used to create a separation between content on the screen

divider { }

Heading

Heading spec

The heading component can be used to provide a title for a step or section. Different sizes can be used to provide a hierarchy within a step.

heading {
    text = "Money for here, there and everywhere"
}

Illustration

Image spec

The image component allows an illustration to be displayed via URN.

media {
    media = Media.Image.build {
        uri = "urn:wise:illustrations:globe"
        accessibilityDescription = "An image"
    }
}

Image

Image spec

Displays an image in the layout. Supports http(s) and data (base64).

media {
    media = Media.Image.build {
        uri = "/staticrab/dynamic-flow-docs/example.jpg"
        accessibilityDescription = "An image"
    }
}

Instructions

Instructions spec

Gives users positive and negative instructions, or explains what a feature does and doesn't do.

instructions {
    items {
        item {
            text = "Make an initial money transfer"
            context = Context.POSITIVE
        }
        item {
            text = "Pay extra hidden fees for transfers"
            context = Context.NEGATIVE
        }
    }
}

List

List spec

A list of elements that don't require interaction.

list {
    title = "Transactions"
    items {
        item {
            title = "Starbucks"
            description = "Coffee"
            supportingValues {
                value = "+100 GBP"
                subvalue = "Refund"
            }
            media = Media.Avatar.build {
                content { uri { uri = "urn:wise:icons:takeaway" } }
            }
        }
        item {
            title = "BBC"
            description = "TV License"
            supportingValues {
                value = "-5 GBP"
                subvalue = "Payment Success"
            }
            media = Media.Avatar.build {
                content {
                    uri {
                        uri = "urn:wise:icons:person"
                        badgeUri = "urn:wise:icons:bills"
                    }
                }
                accessibilityDescription = "An image of the BBC"
            }
        }
        item {
            title = "Pineapple Pizza Express"
            description = "Pizza"
            supportingValues {
                value = "-13 GBP"
                subvalue = "Transaction pending"
            }
            media = Media.Avatar.build {
                content { text { text = "PE" } }
            }
        }
    }
}

Loading indicator

Loading Indicator spec

The loading indicator gives the user an indication that something is happening.

loadingIndicator { }

Markdown

Markdown spec

Markdown is a block of rich content defined using markdown. If you just need plain text, consider using a paragraph.

markdown {
    content = "**Sending money shouldn't cost the earth**. Wise can save you [up to 9x](https://wise.com/help/articles/2974130/how-does-wise-compare-to-leading-banks) compared to UK high street banks."
}

Paragraph

Paragraph spec

Paragraph is a block of plain text content. If you need richer formatting and layout options, consider using the markdown component.

paragraph {
    text = "The Wise account is the universal way for you to manage money internationally. It's made for the world. And it's built to save your money and time, so you can do more of the things you love."
}

Progress

Progress spec

Progress shows the progress of something, visually and with an accompanying description.

progress {
    title = "Send 5,550 GBP"
    description = "You have 3 days to reach your goal."
    progress = 0.55F
    progressText = "200 GBP to go"
}

Review

Review spec

A list of read-only labels and values, useful when displaying information for the user to review.

review {
    title = "Please review your transfer"
    fields {
        field {
            label = "Recipient"
            value = "Florence Jones"
        }
        field {
            label = "Amount"
            value = "$6550"
        }
        field {
            label = "To be sent"
            value = "July 21, 2024"
        }
    }
    callToAction {
        title = "Edit"
        behavior = ActionBehavior.build {
            action {
                url = "/edit"
            }
        }
    }
}

Status list

Status List spec

A list of items with optional states, subtitles, and icons.

statusList {
    items {
        item {
            title = "Set up your card"
            description = "Get set up in seconds with a digital card."
            icon = Icon.Named("chip")
            status = StatusListLayout.Status.DONE
        }
        item {
            title = "Open an account"
            description = "Open a balance in one of 50+ currencies"
            icon = Icon.Named("globe")
        }
    }
}

Form components

Form components give users way to input data. They're used inside forms.

Checkbox

Boolean spec

Checkboxes let users select zero, one, or multiple items from a list.

obj {
    id = "#schema"
    properties {
        boolean("terms") {
            title = "I agree to the terms and conditions"
        }
    }
}

Date

String spec

A date input lets users enter a date.

obj {
    id = "#schema"
    properties {
        string("date") {
            title = "Date of Birth"
            format = StringSchema.Format.DATE
        }
    }
}

Date lookup

String spec

The date picker lets users pick a date from a calendar.

obj {
    id = "#schema"
    properties {
        string("date") {
            title = "Transfer date"
            format = StringSchema.Format.DATE
            control = Control.Schema.String.DATE_LOOKUP
            placeholder = "Select a date"
            minimum = "2023-01-01"
            maximum = "2026-12-31"
        }
    }
}

Form Tabs

One of spec

Tabs let the user choose between different groups of inputs to fill out. These are distinct from Layout tabs, which are only used to control the layout of the page, and do not alter the data which will be sent on submit.

obj {
    id = "#schema"
    properties {
        oneOf("details") {
            title = ""
            control = Control.Schema.OneOf.TAB
            schemas {
                obj {
                    title = "Local"
                    properties {
                        string("account-number") {
                            title = "Account number"
                        }
                        string("sort-code") {
                            title = "Sort code"
                        }
                    }
                }
                obj {
                    title = "International"
                    properties {
                        string("iban") {
                            title = "IBAN"
                        }
                        string("bic") {
                            title = "BIC"
                        }
                    }
                }
            }
        }
    }
}

Integer

Integer spec

Integer fields restrict input to whole numbers only (no decimals).

obj {
    id = "#schema"
    properties {
        integer("age") {
            title = "Age"
        }
    }
}

Number

Number spec

Number fields restrict input to numbers only (including decimals).

obj {
    id = "#schema"
    properties {
        number("amount") {
            title = "Amount"
        }
    }
}

Money Input

Number spec

Money Input allows for textual input of an amount and selection of a currency.

money {
    id = "#schema"
    title = "Sending"
    currencies {
        const {
            value("EUR")
            title = "EUR"
            description = "Euro"
        }
        const {
            value("GBP")
            title = "GBP"
            description = "British pound"
        }
        const {
            value("USD")
            title = "USD"
            description = "US dollar"
        }
    }
}

Number

Number spec

Number fields restrict input to numbers only (including decimals).

obj {
    id = "#schema"
    properties {
        number("amount") {
            title = "Amount"
        }
    }
}

Password

String spec

A password field lets users enter a password securely, by obscuring the text.

obj {
    id = "#schema"
    properties {
        string("password") {
            title = "Password"
            control = Control.Schema.String.PASSWORD
            autocompleteHint(AutocompleteToken.PASSWORD)
        }
    }
}

Phone number

String spec

A phone number input gives users a convenient way to enter their phone number.

obj {
    id = "#schema"
    properties {
        string("phone") {
            title = "Phone number"
            control = Control.Schema.String.PHONE_NUMBER
            autocompleteHint(AutocompleteToken.PHONE_NUMBER)
        }
    }
}

Radio

One of spec

A radio lets users select a single item from a mutually exclusive list.

obj {
    id = "#schema"
    properties {
        oneOf("account") {
            title = "Which account type would you like to open?"
            control = Control.Schema.OneOf.RADIO
            schemas {
                const {
                    title = "Business"
                    value("BUSINESS")
                }
                const {
                    title = "Personal"
                    value("PERSONAL")
                }
            }
        }
    }
}

Repeating items

Array spec

To capture a list of data, you can use a form that can be filled in multiple times. Items can be edited or deleted.

val repeatableStepExample = Step.build {
    id = "Repeatable"
    title = ""
    schemas {
        obj {
            id = "#schema"
            properties {
                list("residency") {
                    title = "Tax residency"
                    addItemTitle = "Add a tax residency"
                    editItemTitle = "Edit a tax residency"
                    items = OneOfSchema.build {
                        schemas {
                            obj {
                                title = "United Kingdom"
                                summary = Summary.Provider(providesMedia = true)
                                media = Media.Avatar.build {
                                    content { uri { uri = "urn:wise:countries:gb:image" } }
                                }
                                properties {
                                    const("country") {
                                        title = "United Kingdom"
                                        value("UK")
                                        summary = Summary.Provider(providesTitle = true)
                                    }
                                }
                            }
                            obj {
                                title = "Spain"
                                summary = Summary.Provider(providesMedia = true)
                                media = Media.Avatar.build {
                                    content { uri { uri = "urn:wise:countries:es:image" } }
                                }
                                properties {
                                    const("country") {
                                        title = "Spain"
                                        value("ES")
                                        summary = Summary.Provider(providesTitle = true)
                                    }
                                    string("taxNumber") {
                                        title = "NIE Number"
                                        summary = Summary.Provider(providesDescription = true)
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    layout {
        form {
            schemaId = "#schema"
        }
    }
    model = encodeToJsonElement(
        mapOf(
            "residency" to listOf(
                mapOf("country" to "UK"),
                mapOf(
                    "country" to "ES",
                    "taxNumber" to "12345678"
                )
            )
        )
    )
}

Segmented control

One of spec

A different treatment for tabular content.

obj {
    id = "#schema"
    properties {
        oneOf("paymentMethod") {
            title = ""
            control = Control.Schema.OneOf.SEGMENTED
            schemas {
                const {
                    title = "ACH"
                    value("ACH")
                }
                const {
                    title = "Wire"
                    value("WIRE")
                }
                const {
                    title = "SWIFT"
                    value("SWIFT")
                }
            }
        }
    }
}

Select

One of spec

A select lets a user pick an option from a list of options (typically used for longer lists).

obj {
    id = "#schema"
    properties {
        oneOf("account") {
            title = "Choose a balance currency"
            schemas {
                const {
                    title = "EUR"
                    media = Media.Avatar.build {
                        content { uri { uri = "urn:wise:currencies:eur:image" } }
                    }
                    description = "Euro"
                    value("EUR")
                    alert {
                        markdown = "This is the default currency for your account."
                        context = Context.NEUTRAL
                    }
                }
                const {
                    title = "GBP"
                    media = Media.Avatar.build {
                        content { uri { uri = "urn:wise:currencies:gbp:image" } }
                    }
                    description = "British pound"
                    value("GBP")
                }
                const {
                    title = "USD"
                    media = Media.Avatar.build {
                        content { uri { uri = "urn:wise:currencies:usd:image" } }
                    }
                    description = "United States dollar"
                    value("USD")
                }
            }
        }
    }
}

Select (multiple)

Array schema

A multi-select lets a user pick multiple options from a list.

obj {
    id = "#schema"
    properties {
        list("currencies") {
            title = "Select your preferred currencies"
            addItemTitle = ""
            editItemTitle = ""
            items = OneOfSchema.build {
                schemas {
                    const {
                        title = "USD"
                        description = "United States dollar"
                        media = Media.Avatar.build {
                            content { uri { uri = "urn:wise:currencies:usd:image" } }
                        }
                        value("USD")
                    }
                    const {
                        title = "GBP"
                        description = "British pound"
                        media = Media.Avatar.build {
                            content { uri { uri = "urn:wise:currencies:gbp:image" } }
                        }
                        value("GBP")
                    }
                    const {
                        title = "EUR"
                        description = "Euro"
                        media = Media.Avatar.build {
                            content { uri { uri = "urn:wise:currencies:eur:image" } }
                        }
                        value("EUR")
                    }
                }
            }
        }
    }
}

Select (multiple, inline)

Array schema

An inline multi-select lets a user pick multiple options from a list that is displayed inline.

obj {
    id = "#schema"
    properties {
        list("currencies") {
            title = "Select your preferred currencies"
            addItemTitle = ""
            editItemTitle = ""
            control = Control.Schema.Array.INLINE
            items = OneOfSchema.build {
                schemas {
                    const {
                        title = "USD"
                        description = "United States dollar"
                        media = Media.Avatar.build {
                            content { uri { uri = "urn:wise:currencies:usd:image" } }
                        }
                        value("USD")
                    }
                    const {
                        title = "GBP"
                        description = "British pound"
                        media = Media.Avatar.build {
                            content { uri { uri = "urn:wise:currencies:gbp:image" } }
                        }
                        value("GBP")
                    }
                    const {
                        title = "EUR"
                        description = "Euro"
                        media = Media.Avatar.build {
                            content { uri { uri = "urn:wise:currencies:eur:image" } }
                        }
                        value("EUR")
                    }
                }
            }
        }
    }
}

Inline checkbox group

Array schema

An inline checkbox group lets a user pick multiple options from a list with checkboxes.

obj {
    id = "#schema"
    properties {
        list("currencies") {
            title = "Select your preferred currencies"
            addItemTitle = ""
            editItemTitle = ""
            control = Control.Schema.Array.INLINE_CHECKBOX_GROUP
            items = OneOfSchema.build {
                schemas {
                    const {
                        title = "USD"
                        description = "United States dollar"
                        media = Media.Avatar.build {
                            content { uri { uri = "urn:wise:currencies:usd:image" } }
                        }
                        value("USD")
                    }
                    const {
                        title = "GBP"
                        description = "British pound"
                        media = Media.Avatar.build {
                            content { uri { uri = "urn:wise:currencies:gbp:image" } }
                        }
                        value("GBP")
                    }
                    const {
                        title = "EUR"
                        description = "Euro"
                        media = Media.Avatar.build {
                            content { uri { uri = "urn:wise:currencies:eur:image" } }
                        }
                        value("EUR")
                    }
                }
            }
        }
    }
}

Switch

Boolean spec

A switch component provides a toggle interface for boolean values, offering an alternative to checkboxes.'

boolean("recurringPayment") {
    title = "Recurring payment"
    control = "switch-item"
    defaultValue = true
}

Text

String spec

A text field lets users enter a single line of text.

obj {
    id = "#schema"
    properties {
        string("name") {
            title = "Full name"
        }
    }
}

Text area

String spec

A text area is a field that lets users enter several lines of text.

obj {
    id = "#schema"
    properties {
        string("feedback") {
            title = "Please leave your comments below"
            control = Control.Schema.String.TEXTAREA
        }
    }
}

Upload

Persist async spec

An upload field allows the user to upload one or more files that fit certain criteria. This example uses persist async, where data is stored to a separate endpoint in exchange for a token. We also support synchronous uploads, but using persist-async is preferred.

obj {
    id = "#schema"
    properties {
        string("file") {
            persistAsync {
                url = "/upload"
                method = HttpMethod.POST
                param = "bodyAttribute"
                idProperty = "token"
                schema = BlobSchema.build {
                    source = Upload.Source.FILE
                    title = "Invoice"
                    description = "PNG, JPG, or PDF, less than 5mb"
                    accepts = listOf(
                        "image/png",
                        "image/jpg",
                        "application/pdf"
                    )
                }
            }
        }
    }
}

Upload (multiple)

Persist async spec

Similar to the upload component, except it allows the upload of multiple files. This example uses persist async, where data is stored to a separate endpoint in exchange for a token. We also support synchronous uploads, but using persist-async is preferred.

obj {
    id = "#schema"
    properties {
        list("files") {
            title = ""
            addItemTitle = ""
            editItemTitle = ""
            items = StringSchema.build {
                persistAsync {
                    url = "/persist"
                    method = HttpMethod.POST
                    param = "file"
                    idProperty = "token"
                    schema = StringSchema.build {
                        source = Upload.Source.FILE
                        format = StringSchema.Format.BASE64URL
                        title = "Source of wealth documents"
                    }
                }
            }
        }
    }
}

Interactive components

These components give the user a way to interact and take actions.

Button

Button spec

A button lets the user perform an action (such as submitting the form) with a tap or a click.

button {
    title = "Continue"
    control = "primary"
    behavior = ActionBehavior.build {
        action {
            url = "/submit"
            method = HttpMethod.POST
        }
    }
}

Decision

Decision spec

Decisions give users a rich way to choose between options and navigate to the next step.

decision {
    title = "Decision title"
    options {
        option {
            title = "Top up"
            description = "Get started by adding money to your balance"
            media = Media.Avatar.build {
                content { uri { uri = "urn:wise:icons:plus" } }
            }
            behavior = ActionBehavior.build {
                action {}
            }
        }
        option {
            title = "Connect your bank account"
            description = "Move money with ease by connecting another account"
            media = Media.Avatar.build {
                content { uri { uri = "urn:wise:icons:bank" } }
            }
            behavior = ActionBehavior.build {
                action {}
            }
        }
    }
}

Decision (filtered)

Decision spec

Filtering can be enabled on Deicsion components to allow easier selection from a long list.

decision {
    title = "Recent Purchases"
    control = "filtered"
    options {
        option {
            title = "Invoice for Consulting"
            description = "Down Under LTD"
            supportingValues {
                value = "100 AUD"
            }
            media = Media.Avatar.build {
                content { uri { uri = "urn:wise:countries:au:image" } }
            }
            behavior = ActionBehavior.build {
                action {
                    url = "/au-currency"
                }
            }
        }
        option {
            title = "TV License"
            description = "British Broadcasting Network"
            supportingValues {
                value = "50 GBP"
            }
            media = Media.Avatar.build {
                content { uri { uri = "urn:wise:countries:gb:image" } }
            }
            behavior = ActionBehavior.build {
                action {
                    url = "/gbp-currency"
                }
            }
        }
        option {
            title = "Enchantment Table"
            description = "Mooshroom Industries"
            supportingValues {
                value = "30 USD"
            }
            media = Media.Avatar.build {
                content { uri { uri = "urn:wise:countries:us:image" } }
            }
            behavior = ActionBehavior.build {
                action {
                    url = "/us-currency"
                }
            }
        }
        option {
            title = "Mjölnir"
            description = "B&S Manufacturing"
            supportingValues {
                value = "10 NOR"
            }
            media = Media.Avatar.build {
                content { uri { uri = "urn:wise:countries:no:image" } }
            }
            behavior = ActionBehavior.build {
                action {
                    url = "/no-currency"
                }
            }
        }
    }
}

Search (async)

Search spec

Component that allows the user to lookup data remotely and select a result based on the search query.

search {
    method = HttpMethod.GET
    url = "/companies"
    param = "search"
    title = "Company"
    emptyMessage = "No company found"
}

Layout components

Layout components give you control over the layout of your screen.

Align

Align spec

Aligns the contents of a layout relative to its parent.

heading {
    align = Align.CENTER
    text = "Money for here, there and everywhere"
}

Box

Box spec

A box wraps other components inside a box.

box {
    width = Size.MD
    control = Control.Layout.Box.BORDERED
    components {
        paragraph {
            text = "Sending money shouldn't cost the earth. That's why we charge you as little as possible when you transfer and exchange money internationally."
        }
    }
}

Columns

Columns spec

Columns give you a way to split content into two vertical sections.

columns {
    left {
        heading {
            text = "Money for here, there and everywhere"
        }
    }
    right {
        paragraph {
            text = "Sending money shouldn't cost the earth. That's why we charge you as little as possible when you transfer and exchange money internationally – you can save up to 9x compared to UK high street banks."
        }
    }
}

Margin

Size spec

Defines the spacing above each component.

heading {
    text = "Money for here, there and everywhere"
}
paragraph {
    margin = Size.LG
    text = "The Wise account is the universal way for you to manage money internationally."
}

Modal

Modal spec

Modals allow you to show dialogs or bottom sheets over your main step.

button {
    title = "Terms and conditions"
    behavior = ModalBehavior.build {
        title = "Terms and conditions"
        content {
            paragraph {
                text = "Welcome to our application. If you continue to browse and use this application, you are agreeing to comply with and be bound by the following terms and conditions of use, which together with our privacy policy govern our relationship with you in relation to this application."
            }
        }
    }
}

Section

Section spec

A section header with an optional action

section {
    title = "Primary Tenant"
    components {
        markdown {
            content = "Simon Claw"
        }
    }
}
section {
    title = "Co-applicant"
    components {
        markdown {
            content = "Tim Cheese"
        }
    }
    callToAction {
        title = "Edit"
        accessibilityDescription = "This is an edit button to edit co-applicant details"
        behavior = ActionBehavior.build {
            action {
                url = "/edit"
                method = HttpMethod.POST
            }
        }
    }
}

Tabs

Tabs spec

Tabs can be used to display content in a tabular form. These are for page layout only - if you want to use form inputs in tabs we recommend using Form tabs.

tabs {
    tabs {
        tab {
            title = "Unpaid"
            components {
                decision {
                    options {
                        option {
                            media = Media.Avatar.build {
                                content { uri { uri = "urn:wise:icons:clock" } }
                            }
                            title = "Joe Bloggs"
                            description = "15 GBP"
                            behavior = ActionBehavior.build {
                                action {
                                    url = "/pay"
                                }
                            }
                        }
                        option {
                            media = Media.Avatar.build {
                                content { uri { uri = "urn:wise:icons:clock" } }
                            }
                            title = "Mary Smith"
                            description = "20 EUR"
                            behavior = ActionBehavior.build {
                                action {
                                    url = "/pay"
                                }
                            }
                        }
                    }
                }
            }
        }
        tab {
            title = "Paid"
            components {
                decision {
                    options {
                        option {
                            media = Media.Avatar.build {
                                content { uri { uri = "urn:wise:icons:check" } }
                            }
                            title = "Jane Doe"
                            description = "10 GBP"
                            behavior = ActionBehavior.build {
                                action {
                                    url = "/view"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Form features

Use these form features to make your form more usable and readable.

Autocomplete hint

Autocomplete hints help browsers, operating systems and assistive technologies understand the type of a field.

string("email") {
    title = "Email"
    autocompleteHint = listOf(AutocompleteToken.EMAIL)
}

Disabled

Set a form field to be disabled.

string("name") {
    title = "Full name"
    disabled = true
}

Form Section

Object spec

Group input components into sections with headings.

obj {
    id = "#schema"
    title = "Personal details"
    properties {
        string("name") {
            title = "Full name"
        }
    }
}

Help

Most schemas support help text, which can be used to provide extra information that may be helpful to users when filling out a field.

string("iban") {
    title = "IBAN"
    help {
        markdown = "IBANs are long account numbers used by banks for cross-border transfers. Each country structures this number differently, but it always starts with a **2 digit country code** (e.g. DE for Germany)."
    }
}

Model

Use a model to pre-fill your form with data.

val modelStepExample = Step.build {
    id = "Model"
    title = ""
    schemas {
        obj {
            id = "#schema"
            required = listOf("name")
            properties {
                string("name") {
                    title = "Name"
                }
            }
        }
    }
    layout {
        form {
            schemaId = "#schema"
        }
    }
    model = encodeToJsonElement(mapOf("name" to "Florence Jones"))
}

Persisting data

Persist async spec

Submit part of a form asynchronously and include a token in its place.

string {
    id = "#schema"
    persistAsync {
        url = "/persist"
        method = HttpMethod.POST
        param = "number"
        idProperty = "token"
        schema = StringSchema.build {
            title = "Card number"
        }
    }
}

Placeholder

Set a placeholder on your form field.

string("name") {
    title = "Name"
    placeholder = "First and last names"
}

Required

Set a form field to be required.

obj {
    id = "#schema"
    required = listOf("name")
    properties {
        string("name") {
            title = "Name"
        }
    }
}

Media

Media represents a visual asset which can be displayed in conjunction with other components. Media can either be an avatar or an image.

Avatar (URI)

Avatars can use assets via URIs. These can be URLs, or known URNs (currently icons and flags are supported). Avatars can also include badges with a URI.

media = Media.Avatar.build {
    content {
        uri {
            uri = "urn:wise:icons:person"
            badgeUri = "urn:wise:icons:plus?=background-color=brand-bright-green"
        }
    }
}

Avatar (Text)

Avatars can also contain text, usually referencing a person or entity by their initials.

item {
    title = "250 GBP to Amigo Burrito"
    description = "Declined — not enough money in your account"
    media = Media.Avatar.build {
        content {
            text {
                text = "AB"
                badgeUri = "urn:wise:icons:plus?=background-color=sentiment-warning"
            }
        }
    }
    inlineAlert {
        content = "Insufficient funds"
        context = Context.WARNING
    }
}

Image

Media can also be an Image, which avoids wrapping the content in an avatar.

media = Media.Image.build {
    uri = "/staticrab/dynamic-flow-docs/example.jpg"
}

Form validation

We have several ways of validating form input, supporting simple, immediate validation and more complex validation that can be performed on the server. See also the Global Error for a way to show a step level error.

Client-side validation

Client side validation. Validation is triggered on blur or submission.

string("password") {
    title = "Please enter a password"
    description = "Must be at least 8 characters long"
    minLength = 8
}

Server-side validation

Include errors in your step to be rendered as errors in your form. Supports form validation errors and global errors. Errors will be cleared on edit.

errors {
    validation = buildJsonObject {
        put("password", "Please use a password you haven't used before")
    }
    error = "Something went wrong. Please try again."
}

Validation messages

Validation messages let you provide custom messages for validation errors.

string("password") {
    title = "Please enter a password"
    description = "Must be at least 8 characters long"
    minLength = 8
    maxLength = 25
    validationMessages = mapOf(
        "minLength" to "Your password is too short.",
        "maxLength" to "Your password is too long."
    )
}

Validation pattern

Use the pattern prop to enforce a pattern on a field. Validation is triggered on blur or submission.

string("accountNumber") {
    title = "Account number"
    description = "This is an 8 digit number starting with 0"
    pattern = "0[0-9]{7}"
    control = Control.Schema.String.NUMERIC
}

Step Features

Back navigation

Back navigation spec

Back navigation can be used to redirect the user when the default back behaviour doesn't make sense.

navigation {
    back {
        title = "Back"
        action {
            url = "/payment-method-selection"
        }
    }
}

Camera

Open the camera to take a picture.

val cameraStepExample = Step.build {
    id = "Camera"
    title = ""
    schemas {
        string {
            id = "#schema"
            persistAsync {
                url = "/persist"
                method = HttpMethod.POST
                param = "image"
                idProperty = "token"
                schema = BlobSchema.build {
                    source = Upload.Source.CAMERA
                    accepts = listOf("image/*")
                }
            }
        }
    }
    layout {
        form {
            schemaId = "#schema"
        }
    }
}

External

External spec

Open a URL in the browser, or in another app on mobile.

val externalStepExample = Step.build {
    id = "External"
    title = ""
    external {
        url = "https://google.com"
    }
    layout {}
}

Global error

Step error spec

Show a general error, not related to any particular schema.

val globalErrorStepExample = Step.build {
    id = "GlobalError"
    title = ""
    layout {}
    errors {
        error = "Something has gone wrong. Please try again soon."
    }
}

Polling

Polling spec

Poll an endpoint while waiting for a server state to change.

val pollingStepExample = Step.build {
    id = "Polling"
    title = "Polling Example"
    polling {
        url = "/polling/get-status?id=12345"
        delay = 5
        timeout = 60
        maxAttempts = 100
        onError {
            behavior = ActionBehavior.build {
                action {}
            }
        }
    }
    layout {}
}

Stack behaviour

Stack behavior spec

This mobile only feature offers more control over how new steps are added to the navigation stack. The default is to push on to the stack, but setting this can change the behaviour to replace, remove previous or even clear the stack.

navigation {
    stackBehavior = Navigation.StackBehavior.REPLACE_CURRENT
}

Toolbar

Toolbar spec

Toolbar is used to display a selection of buttons on the user's toolbar.

toolbar {
    items = listOf(
        Toolbar.Button.build {
            title = "Earn £75"
            control = "primary"
            behavior = ModalBehavior.build {
                title = "Invite your friends"
                content {
                    paragraph {
                        text = "For every 3 friends you refer to Wise, you will earn £75"
                    }
                    button {
                        title = "Close"
                        behavior = DismissBehavior()
                    }
                }
            }
        },
        Toolbar.Button.build {
            title = "Help"
            control = "secondary-neutral"
            media = Media.Avatar(
                accessibilityDescription = "Help",
                content = listOf(
                    Media.Avatar.UriContent(
                        uri = "urn:wise:icons:question-mark-circle",
                        badgeUri = null
                    )
                )
            )
            behavior = LinkBehavior.build {
                url = "https://wise.com/help/articles/2596978/how-do-i-add-money-to-my-account"
            }
        }

Behavior

Behaviors define what happens when a user interacts with an element. They can be attached to various components to customize their functionality.

Action Behavior

Action Behavior spec

Performs an action (like submitting a form) when the behavior is triggered.

button {
    control = "primary"
    title = "Confirm and send"
    behavior = ActionBehavior.build {
        action {
            url = "/transfers/confirm"
            method = HttpMethod.POST
        }
    }
}

Refresh Behavior

Refresh Behavior spec

Refreshes the current step when the behavior is triggered, using the refreshUrl property of Step.

button {
    control = "primary"
    title = "Check Status"
    behavior = RefreshBehavior()
}

Link Behavior

Link Behavior spec

Opens a link when the behavior is triggered.

button {
    control = "tertiary"
    title = "Learn more about Direct Debits"
    behavior = LinkBehavior.build {
        url = "https://wise.com/help/articles/2977956/getting-started-with-direct-debits"
    }
}

Copy Behavior

Copy Behavior spec

Places the provided content into the system clipboard when triggered.

review {
    fields {
        field {
            label = "Cardholder name"
            value = "Emmett Brown"
            callToAction {
                title = "Copy"
                behavior = CopyBehavior.build {
                    content = "Emmett Brown"
                }
            }
        }
        field {
            label = "Card number"
            value = "0118 0135 2610 1985"
            callToAction {
                title = "Copy"
                behavior = CopyBehavior.build {
                    content = "0118 0135 2610 1985"
                }
            }
        }
        field {
            label = "Expiry date"
            value = "10/85"
            callToAction {
                title = "Copy"
                behavior = CopyBehavior.build {
                    content = "10/85"
                }
            }
        }
        field {
            label = "Security code"
            value = "088"
            callToAction {
                title = "Copy"
                behavior = CopyBehavior.build {
                    content = "088"
                }
            }
        }
        field {
            label = "Billing address"
            value = "1646 Riverside Drive, Hill Valley, CA 95420, United States"
        }
    }
}

Modal Behavior

Modal Behavior spec

Opens a modal when the behavior is triggered.

button {
    control = "primary"
    title = "Card details"
    behavior = ModalBehavior.build {
        title = "Card details"
        content {
            review {
                fields {
                    field {
                        label = "Cardholder name"
                        value = "Emmett Brown"
                        callToAction {
                            title = "Copy"
                            behavior = CopyBehavior.build {
                                content = "Emmett Brown"
                            }
                        }
                    }
                    field {
                        label = "Card number"
                        value = "0118 0135 2610 1985"
                        callToAction {
                            title = "Copy"
                            behavior = CopyBehavior.build {
                                content = "0118 0135 2610 1985"
                            }
                        }
                    }
                    field {
                        label = "Expiry date"
                        value = "10/85"
                        callToAction {
                            title = "Copy"
                            behavior = CopyBehavior.build {
                                content = "10/85"
                            }
                        }
                    }
                    field {
                        label = "Security code"
                        value = "088"
                        callToAction {
                            title = "Copy"
                            behavior = CopyBehavior.build {
                                content = "088"
                            }
                        }
                    }
                    field {
                        label = "Billing address"
                        value = "1646 Riverside Drive, Hill Valley, CA 95420, United States"
                    }
                }
            }
        }
    }
}

Dismiss Behavior

Dismiss Behavior spec

Dismisses the top-most modal content when the behavior is triggered.

button {
    title = "Dismiss"
    behavior = DismissBehavior()
}

Success Screens

We often use a splash screen at the end of a flow to confirm the user's action.

Splash

You can specify a Step with a control of "splash". On mobile, this will center the layout vertically. On web, it will add some top margin to the entire layout.

Make sure your heading component specifies "control": "display" to use the correct font.

val splashStepExample = Step.build {
    id = "splash"
    title = ""
    control = Control.Step.SPLASH
    layout {
        media {
            media = Media.Image.build {
                uri = "urn:wise:illustrations:megaphone"
            }
        }
        heading {
            text = "Success!"
            control = Control.Layout.Heading.DISPLAY
            align = Align.CENTER
        }
        paragraph {
            text = "Congratulations! Everything went... according to plan."
            align = Align.CENTER
            margin = Size.XL
        }
    }
    footer {
        button {
            title = "Continue"
            control = Control.Layout.Button.PRIMARY
            behavior = ActionBehavior.build {
                action {
                    exit = true
                }
            }
        }
    }
}

Splash Celebration

You can specify a Step with a control of "splash-celebration". On mobile, this will center the layout vertically and automatically change the theme to "forest-green". On web, the DynamicFlow component cannot set the theme for you. Instead, the onThemeChange callback prop will be triggered; and you can use this to set the theme in your app.

Make sure your heading component specifies "control": "display" to use the correct font.

val splashCelebrationStepExample = Step.build {
    id = "splashcelebration"
    title = ""
    control = Control.Step.SPLASH_CELEBRATION
    layout {
        media {
            media = Media.Image.build {
                uri = "urn:wise:illustrations:plane?+type=animated"
            }
        }
        heading {
            text = "Success!"
            control = Control.Layout.Heading.DISPLAY
            align = Align.CENTER
        }
        paragraph {
            text = "Congratulations! Everything went... according to plan."
            align = Align.CENTER
            margin = Size.XL
        }
    }
    footer {
        button {
            title = "Continue"
            control = Control.Layout.Button.PRIMARY
            behavior = ActionBehavior.build {
                action {
                    exit = true
                }
            }
        }
    }
}