# Fields - agent guide

> Normative extract of the EchoSistema template for the page
> `/componentes/campos`. Copy it verbatim: tag names, props, events and classes
> are contract.
>
> Full guide (rules, tokens and every family): `/agents/echosistema-ui.md`

## Rules that apply before any line of code

- Colour only through tokens (`text-fg1`, `bg-panel`, `border-line`, `.es-i--*`); never a hex, never a Tailwind colour.
- Two axes: the intent gives the colour, the variant consumes it. Never combine them by hand.
- Visible text through `t()`; tag names, props and classes stay literal.
- A component does not fetch: it takes props and emits events.
- An icon alone inside a button needs an `aria-label`.

## Always through `<echo-field>`

It resolves the wiring that falls out of sync first when written by hand: the
label's `for`, the message's `aria-describedby` and `aria-invalid` on error.

```vue
<echo-field
  v-slot="{ id, fieldName, describedBy }"
  :label="t('forms.fields.email')"
  name="email"
  required
  state="is-invalid"
  :message="t('forms.errors.email')"
  hint="00000-000"
>
  <echo-input :id="id" v-model="email" :name="fieldName" :aria-describedby="describedBy"
              type="email" autocomplete="email" />
</echo-field>
```

`state`: `is-valid` `is-invalid` `is-neutral` `is-optional` - they are the CLASS
NAMES, the same ones in the CSS. The state travels down through CSS to the
slot's control: do not repeat `.is-invalid` on the `<echo-input>`.

## Controls

```vue
<echo-input v-model="text" type="email" size="sm" />
<echo-textarea v-model="note" rows="3" />
<echo-select v-model="topic"><option value="a">A</option></echo-select>
<echo-search-select v-model="country" list-id="countries" :options="names" />
<echo-date-picker v-model="day" mode="date" :min="MIN" :max="MAX" :start-year="1990" :label="t('birthday')" />
<echo-phone-input v-model="phone" country="br" @validity="ok = $event" />
```

The date picker follows the theme through `--dp-*` tokens and takes the resolved
theme through `:dark`; the panel is not teleported, so it survives inside a
`<dialog>`.

The date picker's `mode`: `date` `time` `datetime`. A birthday wants
`start-year` - the browser's calendar opens on the current month and gets in the
way.

---

Source: `app/components/echo/{Field,Input,Textarea,Select,SearchSelect,DatePicker,PhoneInput}.vue`
