Files
Olive/.github/skills/add-adaptive-card-form/references/form-patterns.md
T
2026-07-30 18:39:38 +02:00

14 KiB

Common Adaptive Card Form Patterns

Reusable template JSON and handler code for the most common form types in Command Palette extensions.


Simple Text Input Form

A basic form with one or two text fields and a submit button.

TemplateJson

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.6",
    "body": [
        {
            "type": "Input.Text",
            "id": "FirstName",
            "label": "First Name",
            "placeholder": "Enter your first name",
            "isRequired": true,
            "errorMessage": "First name is required"
        },
        {
            "type": "Input.Text",
            "id": "Email",
            "label": "Email Address",
            "placeholder": "user@example.com",
            "style": "Email",
            "isRequired": true,
            "errorMessage": "A valid email is required"
        }
    ],
    "actions": [
        {
            "type": "Action.Submit",
            "title": "Submit"
        }
    ]
}

SubmitForm Handler

public override CommandResult SubmitForm(string payload)
{
    var input = JsonNode.Parse(payload)?.AsObject();
    if (input == null) return CommandResult.GoHome();

    var firstName = input["FirstName"]?.ToString() ?? "";
    var email = input["Email"]?.ToString() ?? "";

    return CommandResult.ShowToast($"Registered {firstName} ({email})");
}

Toggle/Checkbox Form

Use Input.Toggle for boolean on/off settings. Combine with DataJson for dynamic defaults.

TemplateJson

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.6",
    "body": [
        {
            "type": "TextBlock",
            "text": "Preferences",
            "weight": "Bolder",
            "size": "Medium"
        },
        {
            "type": "Input.Toggle",
            "id": "AcceptsTerms",
            "title": "I accept the terms and conditions",
            "valueOn": "true",
            "valueOff": "false",
            "value": "false"
        },
        {
            "type": "Input.Toggle",
            "id": "EnableNotifications",
            "title": "Enable notifications",
            "valueOn": "true",
            "valueOff": "false",
            "value": "${notificationsDefault}"
        },
        {
            "type": "Input.Toggle",
            "id": "DarkMode",
            "title": "Use dark mode",
            "valueOn": "true",
            "valueOff": "false",
            "value": "${darkModeDefault}"
        }
    ],
    "actions": [
        {
            "type": "Action.Submit",
            "title": "Save Preferences"
        }
    ]
}

DataJson (Dynamic Defaults)

DataJson = """
{
    "notificationsDefault": "true",
    "darkModeDefault": "false"
}
""";

SubmitForm Handler

public override CommandResult SubmitForm(string payload)
{
    var input = JsonNode.Parse(payload)?.AsObject();
    if (input == null) return CommandResult.GoHome();

    var accepted = input["AcceptsTerms"]?.ToString() == "true";
    var notifications = input["EnableNotifications"]?.ToString() == "true";
    var darkMode = input["DarkMode"]?.ToString() == "true";

    if (!accepted)
    {
        return CommandResult.ShowToast("You must accept the terms to continue.");
    }

    // Save preferences...
    return CommandResult.ShowToast("Preferences saved!");
}

Choice Set (Dropdown/Radio) Form

Use Input.ChoiceSet for single-select dropdowns or radio buttons.

Compact Style (Dropdown)

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.6",
    "body": [
        {
            "type": "Input.ChoiceSet",
            "id": "Priority",
            "label": "Priority Level",
            "style": "compact",
            "value": "medium",
            "choices": [
                { "title": "Low", "value": "low" },
                { "title": "Medium", "value": "medium" },
                { "title": "High", "value": "high" },
                { "title": "Critical", "value": "critical" }
            ]
        },
        {
            "type": "Input.ChoiceSet",
            "id": "Category",
            "label": "Category",
            "style": "compact",
            "choices": [
                { "title": "Bug Report", "value": "bug" },
                { "title": "Feature Request", "value": "feature" },
                { "title": "Documentation", "value": "docs" },
                { "title": "Question", "value": "question" }
            ]
        }
    ],
    "actions": [
        {
            "type": "Action.Submit",
            "title": "Create Issue"
        }
    ]
}

Expanded Style (Radio Buttons)

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.6",
    "body": [
        {
            "type": "Input.ChoiceSet",
            "id": "Theme",
            "label": "Select a theme",
            "style": "expanded",
            "value": "system",
            "choices": [
                { "title": "Light", "value": "light" },
                { "title": "Dark", "value": "dark" },
                { "title": "System Default", "value": "system" }
            ]
        }
    ],
    "actions": [
        {
            "type": "Action.Submit",
            "title": "Apply"
        }
    ]
}

Multi-Section Form

Combine multiple input types with TextBlock headers to create organized, multi-section forms. Use Action.ShowCard for progressive disclosure of optional sections.

TemplateJson

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.6",
    "body": [
        {
            "type": "TextBlock",
            "text": "Personal Information",
            "weight": "Bolder",
            "size": "Medium",
            "separator": true
        },
        {
            "type": "Input.Text",
            "id": "FullName",
            "label": "Full Name",
            "placeholder": "Enter your full name",
            "isRequired": true,
            "errorMessage": "Name is required"
        },
        {
            "type": "Input.Text",
            "id": "Email",
            "label": "Email",
            "placeholder": "user@example.com",
            "style": "Email"
        },
        {
            "type": "TextBlock",
            "text": "Preferences",
            "weight": "Bolder",
            "size": "Medium",
            "separator": true,
            "spacing": "Large"
        },
        {
            "type": "Input.ChoiceSet",
            "id": "Language",
            "label": "Preferred Language",
            "style": "compact",
            "value": "en",
            "choices": [
                { "title": "English", "value": "en" },
                { "title": "Spanish", "value": "es" },
                { "title": "French", "value": "fr" },
                { "title": "German", "value": "de" }
            ]
        },
        {
            "type": "Input.Toggle",
            "id": "Newsletter",
            "title": "Subscribe to newsletter",
            "valueOn": "true",
            "valueOff": "false",
            "value": "true"
        }
    ],
    "actions": [
        {
            "type": "Action.Submit",
            "title": "Save Profile"
        },
        {
            "type": "Action.ShowCard",
            "title": "Advanced Options",
            "card": {
                "type": "AdaptiveCard",
                "body": [
                    {
                        "type": "Input.Text",
                        "id": "ApiKey",
                        "label": "API Key (optional)",
                        "placeholder": "Enter your API key"
                    },
                    {
                        "type": "Input.Toggle",
                        "id": "DebugMode",
                        "title": "Enable debug mode",
                        "valueOn": "true",
                        "valueOff": "false",
                        "value": "false"
                    }
                ],
                "actions": [
                    {
                        "type": "Action.Submit",
                        "title": "Save All"
                    }
                ]
            }
        }
    ]
}

Feedback Form

A common pattern for collecting user feedback with a multiline text area and a rating.

TemplateJson

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.6",
    "body": [
        {
            "type": "TextBlock",
            "text": "We'd love your feedback!",
            "weight": "Bolder",
            "size": "Medium"
        },
        {
            "type": "TextBlock",
            "text": "Tell us what you think and how we can improve.",
            "wrap": true,
            "spacing": "Small"
        },
        {
            "type": "Input.ChoiceSet",
            "id": "Rating",
            "label": "How would you rate your experience?",
            "style": "expanded",
            "isRequired": true,
            "errorMessage": "Please select a rating",
            "choices": [
                { "title": "⭐ Poor", "value": "1" },
                { "title": "⭐⭐ Fair", "value": "2" },
                { "title": "⭐⭐⭐ Good", "value": "3" },
                { "title": "⭐⭐⭐⭐ Great", "value": "4" },
                { "title": "⭐⭐⭐⭐⭐ Excellent", "value": "5" }
            ]
        },
        {
            "type": "Input.Text",
            "id": "Comments",
            "label": "Comments",
            "placeholder": "Share your thoughts...",
            "isMultiline": true,
            "maxLength": 500
        }
    ],
    "actions": [
        {
            "type": "Action.Submit",
            "title": "Send Feedback"
        }
    ]
}

SubmitForm Handler with Confirmation Dialog

public override CommandResult SubmitForm(string payload)
{
    var input = JsonNode.Parse(payload)?.AsObject();
    if (input == null) return CommandResult.GoHome();

    var rating = input["Rating"]?.ToString() ?? "0";
    var comments = input["Comments"]?.ToString() ?? "";

    return CommandResult.Confirm(new ConfirmationArgs
    {
        Title = "Submit feedback?",
        Description = $"Rating: {rating}/5\n\n{(string.IsNullOrEmpty(comments) ? "No comments" : comments)}",
        PrimaryCommand = new AnonymousCommand(() =>
        {
            // Process and store feedback
            new ToastStatusMessage("Thank you for your feedback!").Show();
        })
        {
            Name = "Submit",
            Result = CommandResult.Dismiss(),
        },
    });
}

Tree Content with Forms (Comment/Reply Pattern)

Use TreeContent to create nested, threaded discussions where each node can contain a form for replies.

Post Content (Tree Node)

internal sealed partial class PostContent : TreeContent
{
    private readonly string _author;
    private readonly string _body;
    private readonly PostReplyForm _replyForm;
    private readonly List<PostContent> _childPosts = [];

    public PostContent(string author, string body)
    {
        _author = author;
        _body = body;
        _replyForm = new PostReplyForm(this);

        TemplateJson = """
        {
            "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
            "type": "AdaptiveCard",
            "version": "1.6",
            "body": [
                {
                    "type": "TextBlock",
                    "text": "${author}",
                    "weight": "Bolder"
                },
                {
                    "type": "TextBlock",
                    "text": "${body}",
                    "wrap": true
                }
            ]
        }
        """;
        DataJson = $$"""{ "author": "{{_author}}", "body": "{{_body}}" }""";
    }

    public override IContent[] GetChildren() => [_replyForm, .. _childPosts];

    public void AddReply(PostContent reply) => _childPosts.Add(reply);
}

Reply Form (Child of Tree Node)

internal sealed partial class PostReplyForm : FormContent
{
    private readonly PostContent _parent;

    public PostReplyForm(PostContent parent)
    {
        _parent = parent;
        TemplateJson = """
        {
            "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
            "type": "AdaptiveCard",
            "version": "1.6",
            "body": [
                {
                    "type": "Input.Text",
                    "id": "ReplyText",
                    "placeholder": "Write a reply...",
                    "isMultiline": true
                }
            ],
            "actions": [
                {
                    "type": "Action.Submit",
                    "title": "Reply"
                }
            ]
        }
        """;
    }

    public override CommandResult SubmitForm(string payload)
    {
        var input = JsonNode.Parse(payload)?.AsObject();
        if (input == null) return CommandResult.GoHome();

        var replyText = input["ReplyText"]?.ToString();
        if (!string.IsNullOrWhiteSpace(replyText))
        {
            _parent.AddReply(new PostContent("You", replyText));
        }

        return CommandResult.KeepOpen();
    }
}

Hosting the Thread on a ContentPage

internal sealed partial class ThreadPage : ContentPage
{
    private readonly PostContent _rootPost;

    public ThreadPage()
    {
        Name = "Discussion";
        Title = "Discussion Thread";
        Icon = new IconInfo("\uE90A");

        _rootPost = new PostContent("Alice", "Has anyone tried the new API?");
        _rootPost.AddReply(new PostContent("Bob", "Yes! It works great."));
    }

    public override IContent[] GetContent() => [_rootPost];
}