webfluent 0.4.0-alpha

The Web-First Language — compiles to HTML, CSS, JavaScript, and PDF. 50+ built-in components, reactivity, routing, i18n, SSG, and template engine.
Documentation
# WebFluent Accessibility Linting Specification

> Version 1.0 — Draft
> Author: Monzer Omer
> Date: 2026-03-24

---

## Overview

WebFluent performs **compile-time accessibility checks** on every build. When you run `webfluent build`, the compiler analyzes your `.wf` files and emits warnings for common accessibility violations. Warnings are printed to the console but **do not block the build** — your app still compiles.

This catches issues like missing alt text, unlabeled form controls, and skipped heading levels before they reach users.

---

## 1. How It Works

The accessibility linter runs automatically during `webfluent build`, after parsing and before code generation:

```
.wf files → Lexer → Parser → ⚡ A11y Linter ⚡ → Codegen → Output
```

Warnings appear in the console:

```
Warning [A01]: Image missing "alt" attribute at src/pages/Home.wf:12:5
  Add alt text: Image(src: "photo.jpg", alt: "Description of image")

Warning [A04]: Checkbox missing "label" attribute at src/pages/Settings.wf:8:9
  Add a label: Checkbox(bind: agreed, label: "I agree to terms")

Build complete with 2 accessibility warnings.
```

---

## 2. Lint Rules

### A01 — Image missing `alt`

Every `Image` must have an `alt` attribute for screen readers and when the image fails to load.

```wf
// Bad — triggers warning
Image(src: "/photo.jpg")

// Good
Image(src: "/photo.jpg", alt: "Team photo from the 2026 retreat")

// Decorative image — use empty alt
Image(src: "/divider.png", alt: "")
```

### A02 — IconButton missing accessible label

`IconButton` renders only an icon with no visible text. It must have a `label` attribute so screen readers can announce its purpose.

```wf
// Bad — triggers warning
IconButton(icon: "close")

// Good
IconButton(icon: "close", label: "Close dialog")
```

### A03 — Input missing label

Every `Input` should have a `label` or at minimum a `placeholder` so users know what to type.

```wf
// Bad — triggers warning
Input(text)

// Good
Input(text, label: "Username")
Input(text, placeholder: "Enter your name")
```

### A04 — Form control missing label

`Checkbox`, `Radio`, `Switch`, and `Slider` must have a `label` attribute.

```wf
// Bad — triggers warning
Checkbox(bind: agreed)
Switch(bind: darkMode)

// Good
Checkbox(bind: agreed, label: "I agree to the terms")
Switch(bind: darkMode, label: "Dark mode")
```

### A05 — Button has no text content

Every `Button` needs visible text (first positional argument) so users know what it does.

```wf
// Bad — triggers warning
Button()

// Good
Button("Save")
Button("Delete", danger)
```

### A06 — Link has no text content

Every `Link` should contain text content (children) or a `label` attribute.

```wf
// Bad — triggers warning
Link(to: "/about") {}

// Good
Link(to: "/about") { Text("About Us") }
```

### A07 — Heading is empty

Headings must have text content.

```wf
// Bad — triggers warning
Heading("", h1)

// Good
Heading("Welcome", h1)
```

### A08 — Modal/Dialog missing title

`Modal` and `Dialog` should have a `title` attribute so screen readers announce what the dialog is about.

```wf
// Bad — triggers warning
Modal(visible: showModal) { Text("Content") }

// Good
Modal(visible: showModal, title: "Confirm Action") { Text("Are you sure?") }
```

### A09 — Video missing controls

`Video` elements should have `controls` enabled so users can play/pause/seek.

```wf
// Bad — triggers warning
Video(src: "/intro.mp4")

// Good
Video(src: "/intro.mp4", controls: true)
```

### A10 — Table missing header row

`Table` should contain a `Thead` element with column headers.

```wf
// Bad — triggers warning
Table {
    Trow { Tcell("Alice") Tcell("Admin") }
}

// Good
Table {
    Thead { Tcell("Name") Tcell("Role") }
    Trow { Tcell("Alice") Tcell("Admin") }
}
```

### A11 — Heading levels should not skip

Heading levels should follow a logical order: h1 → h2 → h3. Skipping from h1 to h3 creates confusion for screen reader users navigating by headings.

```wf
// Bad — triggers warning (skips h2)
Heading("Title", h1)
Heading("Section", h3)

// Good
Heading("Title", h1)
Heading("Section", h2)
Heading("Subsection", h3)
```

### A12 — Page should have exactly one h1

Each page should have one (and only one) `h1` heading as its main title.

```wf
// Bad — no h1
Page Home (path: "/") {
    Heading("Welcome", h2)
}

// Bad — multiple h1
Page Home (path: "/") {
    Heading("Title One", h1)
    Heading("Title Two", h1)
}

// Good
Page Home (path: "/") {
    Heading("Welcome", h1)
    Heading("Features", h2)
}
```

---

## 3. Warning Format

All warnings follow this format:

```
Warning [<ID>]: <message> at <file>:<line>:<column>
  <hint>
```

- **ID**: Rule identifier (A01–A12)
- **message**: What's wrong
- **file:line:column**: Exact source location
- **hint**: How to fix it

---

## 4. Build Output

After all warnings are printed, the build summary includes a count:

```
Building MyApp...
  Warning [A01]: Image missing "alt" attribute at src/pages/Home.wf:12:5
  Warning [A03]: Input missing "label" attribute at src/pages/Form.wf:8:9
  3 pages, 2 components, 1 stores
  Build complete with 2 accessibility warnings.
```

If there are no warnings:

```
Building MyApp...
  3 pages, 2 components, 1 stores
  Build complete.
```

---

## 5. Limitations

- **Dynamic values**: If an attribute value comes from a variable (`alt: altText`), the linter accepts it as present — it cannot verify the runtime value is non-empty.
- **User-defined components**: Custom components are not analyzed for internal accessibility. Only built-in components are checked.
- **Complex nesting**: The linter checks direct children but does not deeply traverse user component trees.