Back to Examples

Accessible Form

A form demonstrating a11y best practices with proper headings, aria-labels, input validation, and CSS transitions.

Try in Playground

Features Used

  • a11y attributes
  • Heading hierarchy
  • CSS transitions
  • Form handling

How to Run

npm install @constela/core @constela/runtime
npx constela run accessible-form.json

Source Code

json
{
  "version": "1.0",
  "state": {
    "name": {
      "type": "string",
      "initial": ""
    },
    "email": {
      "type": "string",
      "initial": ""
    },
    "submitted": {
      "type": "boolean",
      "initial": false
    },
    "error": {
      "type": "string",
      "initial": ""
    }
  },
  "actions": [
    {
      "name": "updateName",
      "steps": [
        {
          "do": "set",
          "target": "name",
          "value": {
            "expr": "var",
            "name": "value"
          }
        }
      ]
    },
    {
      "name": "updateEmail",
      "steps": [
        {
          "do": "set",
          "target": "email",
          "value": {
            "expr": "var",
            "name": "value"
          }
        }
      ]
    },
    {
      "name": "submit",
      "steps": [
        {
          "do": "if",
          "condition": {
            "expr": "bin",
            "op": "==",
            "left": {
              "expr": "state",
              "name": "name"
            },
            "right": {
              "expr": "lit",
              "value": ""
            }
          },
          "then": [
            {
              "do": "set",
              "target": "error",
              "value": {
                "expr": "lit",
                "value": "Please fill in all fields."
              }
            }
          ],
          "else": [
            {
              "do": "if",
              "condition": {
                "expr": "bin",
                "op": "==",
                "left": {
                  "expr": "state",
                  "name": "email"
                },
                "right": {
                  "expr": "lit",
                  "value": ""
                }
              },
              "then": [
                {
                  "do": "set",
                  "target": "error",
                  "value": {
                    "expr": "lit",
                    "value": "Please fill in all fields."
                  }
                }
              ],
              "else": [
                {
                  "do": "set",
                  "target": "error",
                  "value": {
                    "expr": "lit",
                    "value": ""
                  }
                },
                {
                  "do": "set",
                  "target": "submitted",
                  "value": {
                    "expr": "lit",
                    "value": true
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ],
  "view": {
    "kind": "element",
    "tag": "div",
    "props": {
      "style": {
        "expr": "lit",
        "value": "font-family: system-ui, sans-serif; padding: 24px; max-width: 480px;"
      }
    },
    "children": [
      {
        "kind": "element",
        "tag": "h1",
        "props": {
          "style": {
            "expr": "lit",
            "value": "margin: 0 0 4px 0; font-size: 24px;"
          }
        },
        "children": [
          {
            "kind": "text",
            "value": {
              "expr": "lit",
              "value": "Contact Us"
            }
          }
        ]
      },
      {
        "kind": "element",
        "tag": "h2",
        "props": {
          "style": {
            "expr": "lit",
            "value": "margin: 0 0 16px 0; font-size: 16px; color: #666; font-weight: normal;"
          }
        },
        "children": [
          {
            "kind": "text",
            "value": {
              "expr": "lit",
              "value": "Your Information"
            }
          }
        ]
      },
      {
        "kind": "element",
        "tag": "div",
        "props": {
          "style": {
            "expr": "lit",
            "value": "display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px;"
          }
        },
        "children": [
          {
            "kind": "element",
            "tag": "input",
            "props": {
              "type": {
                "expr": "lit",
                "value": "text"
              },
              "aria-label": {
                "expr": "lit",
                "value": "Your name"
              },
              "placeholder": {
                "expr": "lit",
                "value": "Your name"
              },
              "value": {
                "expr": "state",
                "name": "name"
              },
              "onInput": {
                "event": "input",
                "action": "updateName"
              },
              "style": {
                "expr": "lit",
                "value": "padding: 8px 12px; border: 1px solid #ccc; border-radius: 6px; font-size: 14px;"
              }
            }
          },
          {
            "kind": "element",
            "tag": "input",
            "props": {
              "type": {
                "expr": "lit",
                "value": "email"
              },
              "aria-label": {
                "expr": "lit",
                "value": "Email address"
              },
              "placeholder": {
                "expr": "lit",
                "value": "Email address"
              },
              "value": {
                "expr": "state",
                "name": "email"
              },
              "onInput": {
                "event": "input",
                "action": "updateEmail"
              },
              "style": {
                "expr": "lit",
                "value": "padding: 8px 12px; border: 1px solid #ccc; border-radius: 6px; font-size: 14px;"
              }
            }
          }
        ]
      },
      {
        "kind": "element",
        "tag": "button",
        "props": {
          "onClick": {
            "event": "click",
            "action": "submit"
          },
          "style": {
            "expr": "lit",
            "value": "padding: 10px 24px; background: #0070f3; color: white; border: none; border-radius: 6px; font-size: 14px; cursor: pointer;"
          }
        },
        "children": [
          {
            "kind": "text",
            "value": {
              "expr": "lit",
              "value": "Submit"
            }
          }
        ]
      },
      {
        "kind": "if",
        "condition": {
          "expr": "bin",
          "op": "!=",
          "left": {
            "expr": "state",
            "name": "error"
          },
          "right": {
            "expr": "lit",
            "value": ""
          }
        },
        "then": {
          "kind": "element",
          "tag": "div",
          "props": {
            "style": {
              "expr": "lit",
              "value": "margin-top: 16px; padding: 12px 16px; background: #ffebee; color: #c62828; border-radius: 6px; font-size: 14px;"
            }
          },
          "children": [
            {
              "kind": "text",
              "value": {
                "expr": "state",
                "name": "error"
              }
            }
          ]
        }
      },
      {
        "kind": "if",
        "condition": {
          "expr": "state",
          "name": "submitted"
        },
        "then": {
          "kind": "element",
          "tag": "div",
          "props": {
            "style": {
              "expr": "lit",
              "value": "margin-top: 16px; padding: 12px 16px; background: #e8f5e9; color: #2e7d32; border-radius: 6px; font-size: 14px;"
            }
          },
          "children": [
            {
              "kind": "text",
              "value": {
                "expr": "lit",
                "value": "Thank you for your submission!"
              }
            }
          ]
        },
        "transition": {
          "enter": "fade-enter",
          "enterActive": "fade-enter-active",
          "exit": "fade-exit",
          "exitActive": "fade-exit-active",
          "duration": 300
        }
      }
    ]
  }
}