formstack 2.0.0
formstack: ^2.0.0 copied to clipboard
A flutter library to create a UI based on the model from either json or dart object.
FormStack #
A powerful Flutter library for building dynamic forms and surveys from Dart objects or JSON. Supports 28 input types, 30+ validators, conditional navigation, nested steps, and full JSON schema configuration.
Installation #
dependencies:
formstack: ^2.0.0
flutter pub get
Quick Start #
import 'package:formstack/formstack.dart';
// Build and render a form
FormStack.api().form(steps: [
QuestionStep(
title: "Your Name",
inputType: InputType.name,
id: GenericIdentifier(id: "name"),
),
QuestionStep(
title: "Email",
inputType: InputType.email,
id: GenericIdentifier(id: "email"),
),
CompletionStep(
title: "Done!",
id: GenericIdentifier(id: "done"),
onFinish: (result) => print(result),
),
]);
// In your widget
Scaffold(body: FormStack.api().render());
Or load from JSON:
await FormStack.api().loadFromAsset('assets/form.json');
Scaffold(body: FormStack.api().render());
Supported Input Types #
Text Inputs #
| InputType | Description | Keyboard | Validation |
|---|---|---|---|
email |
Email address | Email keyboard | Regex email validation |
name |
Person name | Text, auto-capitalize words | Letters only, min 2 chars |
password |
Secure password | Visible password | Uppercase, lowercase, digit, special char, 8+ |
text |
General text | Multiline | Non-empty, configurable numberOfLines |
number |
Numeric input | Number pad | Digits only, supports mask formatting |
phone |
Phone with country code | Phone | Country code dropdown + E.164 format |
currency |
Money amount | Decimal number | Currency symbol prefix, formatted input |
Date & Time #
| InputType | Description | Result Type |
|---|---|---|
date |
Date picker | DateTime |
time |
Time picker | DateTime |
dateTime |
Combined date + time | DateTime |
Choice Inputs #
| InputType | Description | Selection Styles |
|---|---|---|
singleChoice |
Select one option | arrow, tick, toggle |
multipleChoice |
Select multiple | tick, toggle |
dropdown |
Dropdown menu | Standard dropdown |
ranking |
Drag-to-reorder list | Reorderable with rank numbers |
Survey & Rating #
| InputType | Description | Result Type |
|---|---|---|
slider |
Range slider | double (configurable min/max/step) |
rating |
Star rating | int (1 to N stars) |
nps |
Net Promoter Score (0-10) | int (color-coded scale) |
smile |
Emoji satisfaction | int (1-5 scale) |
Media & Files #
| InputType | Description | Result Type |
|---|---|---|
file |
File picker with filters | PlatformFile |
avatar |
Circular image upload | String (base64) |
banner |
Rectangular image upload | String (base64) |
signature |
Draw signature on canvas | String (base64 PNG) |
mapLocation |
Google Maps picker | Location coordinates |
Special #
| InputType | Description | Result Type |
|---|---|---|
otp |
Multi-digit OTP entry | int |
consent |
Checkbox with agreement text | bool |
dynamicKeyValue |
Add/remove key-value pairs | List<KeyValue> |
htmlEditor |
Rich text editor | String |
Step Types #
InstructionStep #
Welcome screens, information pages, instructions.
InstructionStep(
id: GenericIdentifier(id: "welcome"),
title: "Customer Survey",
text: "This will take about 2 minutes",
cancellable: false,
display: Display.medium,
)
QuestionStep #
The main input step. Supports all 28 input types.
QuestionStep(
id: GenericIdentifier(id: "email"),
title: "Email Address",
text: "We'll send your receipt here",
inputType: InputType.email,
inputStyle: InputStyle.outline,
isOptional: false,
hint: "you@example.com",
)
CompletionStep #
Form completion with loading/success/error animations.
CompletionStep(
id: GenericIdentifier(id: "done"),
title: "Submitting...",
autoTrigger: false,
onFinish: (result) => print("Result: $result"),
onBeforeFinishCallback: (result) async {
await submitToApi(result);
return true; // false shows error animation
},
)
NestedStep #
Multiple fields on a single screen.
NestedStep(
id: GenericIdentifier(id: "contact"),
title: "Contact Information",
verticalPadding: 10,
validationExpression: "",
steps: [
QuestionStep(title: "", inputType: InputType.name, label: "First Name",
id: GenericIdentifier(id: "first"), width: 400),
QuestionStep(title: "", inputType: InputType.name, label: "Last Name",
id: GenericIdentifier(id: "last"), width: 400),
QuestionStep(title: "", inputType: InputType.email, label: "Email",
id: GenericIdentifier(id: "email"), width: 400),
],
)
DisplayStep #
Show web content or data lists.
DisplayStep(
id: GenericIdentifier(id: "info"),
url: "https://example.com/terms",
displayStepType: DisplayStepType.web,
)
Validation #
Built-in Validators #
// Text validators
ResultFormat.email("Invalid email")
ResultFormat.name("Invalid name")
ResultFormat.password("Weak password")
ResultFormat.text("Required")
ResultFormat.number("Must be a number")
ResultFormat.phone("Invalid phone")
ResultFormat.url("Invalid URL")
// Numeric range validators
ResultFormat.min("Must be at least 18", 18)
ResultFormat.max("Cannot exceed 100", 100)
ResultFormat.range("Must be 1-10", 1, 10)
ResultFormat.age("Invalid age (0-150)")
ResultFormat.percentage("Must be 0-100")
// String length validators
ResultFormat.minLength("Too short", 3)
ResultFormat.maxLength("Too long", 50)
ResultFormat.pattern("Invalid format", r'^[A-Z]{3}\d{4}$')
ResultFormat.length("Must be exactly 6 digits", 6)
// Choice validators
ResultFormat.singleChoice("Please select one")
ResultFormat.multipleChoice("Select at least one")
ResultFormat.minSelections("Select at least 2", 2)
ResultFormat.maxSelections("Select at most 3", 3)
// Specialty validators
ResultFormat.creditCard("Invalid card number") // Luhn algorithm
ResultFormat.ssn("Invalid SSN") // ###-##-####
ResultFormat.zipCode("Invalid zip") // #####(-####)
ResultFormat.iban("Invalid IBAN") // ISO 13616
ResultFormat.consent("You must agree") // Must be true
ResultFormat.fileSize("File too large", 5242880) // Max bytes
ResultFormat.notNull("Required")
ResultFormat.notBlank("Cannot be empty")
ResultFormat.notEmpty("List cannot be empty")
// Custom validator
ResultFormat.custom("Must start with 'hello'",
(value) => value.startsWith('hello'))
// Compose multiple validators
ResultFormat.compose([
ResultFormat.minLength("Too short", 3),
ResultFormat.maxLength("Too long", 50),
ResultFormat.pattern("Letters only", r'^[a-zA-Z\s]+$'),
])
Styling #
Input Styles #
inputStyle: InputStyle.basic // Flat, no border
inputStyle: InputStyle.outline // Full border
inputStyle: InputStyle.underLined // Bottom border only
Component Styles #
componentsStyle: ComponentsStyle.minimal // Clean, minimal
componentsStyle: ComponentsStyle.basic // Card-style with background
Display Sizes #
display: Display.small // Compact
display: Display.normal // Standard (default)
display: Display.medium // Larger headings
display: Display.large // Big text
display: Display.extraLarge // Maximum size
Selection Types (for choices) #
selectionType: SelectionType.arrow // Navigate arrows
selectionType: SelectionType.tick // Checkmark
selectionType: SelectionType.toggle // Switch toggle
selectionType: SelectionType.dropdown // Dropdown menu
Custom Theme #
QuestionStep(
title: "Styled",
inputType: InputType.text,
style: UIStyle(
Colors.indigo, // Button background
Colors.white, // Button foreground
Colors.indigo, // Input border color
8.0, // Title bottom padding
12.0, // Button border radius
),
nextButtonText: "Continue",
backButtonText: "Previous",
)
Conditional Navigation #
Route users to different steps based on their answers:
QuestionStep(
id: GenericIdentifier(id: "role"),
title: "Your Role",
inputType: InputType.singleChoice,
autoTrigger: true,
options: [
Options("dev", "Developer"),
Options("designer", "Designer"),
],
relevantConditions: [
ExpressionRelevant(
identifier: GenericIdentifier(id: "dev_questions"),
expression: "IN dev",
),
ExpressionRelevant(
identifier: GenericIdentifier(id: "design_questions"),
expression: "IN designer",
),
],
)
Expression syntax:
IN value- Result contains the valueNOT_IN value- Result does not contain the valueFOR_ALL- Always matches (converge paths)= value/!= value- Exact match
Cross-form navigation with formName:
ExpressionRelevant(
identifier: GenericIdentifier(id: "step_id"),
expression: "IN selected_option",
formName: "another_form", // Navigate to a different form
)
API Reference #
FormStack #
// Create/get instance (supports named instances)
FormStack.api()
FormStack.api(name: "myForm")
// Build form from Dart objects
FormStack.api().form(steps: [...], name: "formName")
// Load from JSON
await FormStack.api().loadFromAsset('assets/form.json')
await FormStack.api().loadFromAssets(['assets/a.json', 'assets/b.json'])
// Render
FormStack.api().render()
FormStack.api().render(name: "formName")
// Pre-fill data
FormStack.api().setResult({"email": "user@test.com"}, formName: "myForm")
// Set validation error on a field
FormStack.api().setError(GenericIdentifier(id: "email"), "Already taken", formName: "myForm")
// Update options dynamically
FormStack.api().setOptions([Options("a", "A")], GenericIdentifier(id: "choice"))
// Disable specific fields
FormStack.api().setDisabledUI(["field_id_1", "field_id_2"])
// Completion callback with async pre-validation
FormStack.api().addCompletionCallback(
GenericIdentifier(id: "done"),
formName: "myForm",
onFinish: (result) => print(result),
onBeforeFinishCallback: (result) async {
final success = await api.submit(result);
return success; // false = show error animation
},
);
// Progress tracking
double progress = FormStack.api().getFormProgress(); // 0.0 - 1.0
int step = FormStack.api().getCurrentStepIndex();
bool done = FormStack.api().isFormCompleted();
Map stats = FormStack.api().getFormStats();
// stats: {totalSteps, completedSteps, requiredSteps, optionalSteps, progress, isCompleted}
// Back navigation control
FormStack.api().systemBackNavigation(true, () => print("Back pressed"));
// Cleanup
FormStack.clearForms();
FormStack.api().clearConfiguration();
JSON Schema #
All step types and properties are supported in JSON. Wrap forms in a named object:
{
"my_form": {
"backgroundAnimationFile": "assets/bg.json",
"steps": [
{
"type": "InstructionStep",
"id": "welcome",
"title": "Welcome",
"text": "Complete this survey",
"cancellable": false,
"display": "medium"
},
{
"type": "QuestionStep",
"id": "name",
"title": "Full Name",
"inputType": "name",
"inputStyle": "outline",
"isOptional": false,
"hint": "John Doe",
"helperText": "Enter your legal name"
},
{
"type": "QuestionStep",
"id": "satisfaction",
"title": "How satisfied are you?",
"inputType": "slider",
"minValue": 0,
"maxValue": 10,
"stepValue": 1,
"defaultValue": 5
},
{
"type": "QuestionStep",
"id": "rating",
"title": "Rate our service",
"inputType": "rating",
"ratingCount": 5
},
{
"type": "QuestionStep",
"id": "recommend",
"title": "Would you recommend us?",
"inputType": "nps"
},
{
"type": "QuestionStep",
"id": "country",
"title": "Country",
"inputType": "dropdown",
"componentsStyle": "basic",
"options": [
{"key": "US", "title": "United States"},
{"key": "UK", "title": "United Kingdom"},
{"key": "IN", "title": "India"}
]
},
{
"type": "QuestionStep",
"id": "phone",
"title": "Phone",
"inputType": "phone",
"phoneCountryCode": "+1"
},
{
"type": "QuestionStep",
"id": "budget",
"title": "Budget",
"inputType": "currency",
"currencySymbol": "$"
},
{
"type": "QuestionStep",
"id": "priorities",
"title": "Rank by priority",
"inputType": "ranking",
"options": [
{"key": "speed", "title": "Speed"},
{"key": "quality", "title": "Quality"},
{"key": "cost", "title": "Cost"}
]
},
{
"type": "QuestionStep",
"id": "agree",
"title": "Agreement",
"inputType": "consent",
"consentText": "I agree to the terms and conditions"
},
{
"type": "QuestionStep",
"id": "sig",
"title": "Signature",
"inputType": "signature"
},
{
"type": "CompletionStep",
"id": "done",
"title": "Thank you!",
"autoTrigger": true
}
]
}
}
Supported JSON Fields #
All Steps: type, id, title, text, description, hint, label, display, isOptional, cancellable, disabled, nextButtonText, backButtonText, cancelButtonText, footerBackButton, componentsStyle, crossAxisAlignmentContent, titleIconAnimationFile, titleIconMaxWidth, width, helperText, defaultValue, semanticLabel, style
QuestionStep: inputType, inputStyle, options, selectionType, autoTrigger, numberOfLines, count, maxCount, mask, filter, maxHeight, lengthLimit, textAlign, relevantConditions, minValue, maxValue, stepValue, minSelections, maxSelections, consentText, currencySymbol, phoneCountryCode, ratingCount
CompletionStep: autoTrigger, successLottieAssetsFilePath, loadingLottieAssetsFilePath, errorLottieAssetsFilePath
NestedStep: steps, validationExpression, verticalPadding
DisplayStep: url, displayStepType, data
Examples #
The example app demonstrates all features across 9 demo screens:
| Demo | Features |
|---|---|
| All Input Types | All 28 input types with descriptions |
| Styles & Display | InputStyle, ComponentsStyle, Display sizes, UIStyle |
| Selection Types | Arrow, tick, toggle, dropdown |
| Validation | Email, password, phone, URL, age, zip, custom, compose |
| Conditional Nav | ExpressionRelevant branching and path convergence |
| Nested Steps | Multi-field screens with cross-field validation |
| API Features | setResult, setError, callbacks, progress tracking |
| Survey Components | Slider, rating, NPS, consent, signature, ranking, phone, currency |
| Load from JSON | Multi-file JSON loading with form linking |
Run the example:
cd example
flutter run
Architecture #
lib/
formstack.dart # Public API exports
src/
formstack.dart # FormStack singleton API
formstack_form.dart # Form navigation and state
input_types.dart # InputType enum (28 values)
core/
form_step.dart # Base FormStep class, enums
parser.dart # JSON parser
ui_style.dart # UIStyle, HexColor
step/
question_step.dart # QuestionStep (all input types)
completion_step.dart # CompletionStep (finish with animation)
instruction_step.dart # InstructionStep (info screens)
nested_step.dart # NestedStep (multi-field)
display_step.dart # DisplayStep (web/list content)
pop_step.dart # PopStep (navigation)
result/
result_format.dart # 30+ validators
common_result.dart # Options, KeyValue, DynamicData
identifiers.dart # GenericIdentifier, StepIdentifier
relevant/
relevant_condition.dart # RelevantCondition base
expression_relevant_condition.dart # Expression-based routing
dynamic_relevant_condition.dart # Callback-based routing
ui/views/input/
text_input_field.dart # Text/email/password/number
choice_input_field.dart # Single/multiple choice
date_input_field.dart # Date/time pickers
slider_input_field.dart # Range slider
rating_input_field.dart # Star rating
nps_input_field.dart # Net Promoter Score
consent_input_field.dart # Checkbox consent
signature_input_field.dart # Signature pad
ranking_input_field.dart # Drag-to-reorder
phone_input_field.dart # Phone with country code
currency_input_field.dart # Currency input
otp_input_field.dart # OTP digits
smile_input_field.dart # Emoji rating
image_input_field.dart # Avatar/banner upload
dynamic_key_value_field.dart # Key-value pairs
map_input_field.dart # Google Maps
html_input_field.dart # Rich text editor
License #
MIT License. See LICENSE for details.