Most forms render raw <select> and <input> elements with local styles instead of the shared components, so fields look different from page to page.
Select is used in 7 files while 35 render a raw <select>. Input is used in 5 while 42 render a raw <input>. 46 files restyle fields locally.
The difference shows: Capacity and Metrics use the shared ▾ (Select.svelte:57,69), while Leaderboard, Matches, Activity and Provisioning show the browser's chevron.
ui-patterns.md:10 says to use Field with Input, Select or Textarea, but check-ui-styles.mjs only checks font sizes (:13), so nothing catches raw controls.
Screenshots
Before, Metrics uses the shared Select:
Before, Leaderboard uses a raw select with the browser chevron:
After (mockup), Leaderboard with the shared style:
Fix
Move raw text inputs and selects onto Field with Input or Select
Extend check-ui-styles.mjs to flag raw <select> and text <input> outside the primitives
Most forms render raw `<select>` and `<input>` elements with local styles instead of the shared components, so fields look different from page to page.
- `Select` is used in 7 files while 35 render a raw `<select>`. `Input` is used in 5 while 42 render a raw `<input>`. 46 files restyle fields locally.
- The difference shows: Capacity and Metrics use the shared ▾ (`Select.svelte:57,69`), while Leaderboard, Matches, Activity and Provisioning show the browser's chevron.
- `ui-patterns.md:10` says to use `Field` with `Input`, `Select` or `Textarea`, but `check-ui-styles.mjs` only checks font sizes (`:13`), so nothing catches raw controls.
**Screenshots**
Before, Metrics uses the shared Select:

Before, Leaderboard uses a raw select with the browser chevron:

After (mockup), Leaderboard with the shared style:

**Fix**
- [x] Move raw text inputs and selects onto `Field` with `Input` or `Select`
- [x] Extend `check-ui-styles.mjs` to flag raw `<select>` and text `<input>` outside the primitives
Reviewed cc9c10226e. Every select and text-like input now goes through Field with Select or Input, and the style check rejects raw ones, so both fix items are done and I'm closing this.
Checklist
Move raw text inputs and selects onto the shared components. Before this commit, 35 files rendered a raw <select>, 42 a raw <input>, 7 used Select and 5 used Input, matching the issue. Now no file outside the primitives renders a raw <select>, and the only raw <input>s left are checkboxes, radios and file pickers. 41 files use Select and 40 use Input. A comparison of every old control with its replacement found the IDs, names, autocomplete values, required, length and pattern limits, ARIA attributes, bindings, handlers and option values unchanged, including on the login and setup forms. In the preview, every visible select on Leaderboard, Matches, Activity, Provisioning, Metrics and Capacity now has the shared ▾ (Select.svelte:55,67) instead of the browser's chevron.
Make check-ui-styles.mjs flag raw controls.checkFormControls parses each file's markup with the Svelte compiler and flags every <select>, and every <input> whose type is missing, dynamic or not in the native list at check-ui-styles.mjs:6-17 (check-ui-styles.mjs:23-69). Only primitives/Input.svelte and primitives/Select.svelte are exempt (check-ui-styles.mjs:26). I ran it on a sample with a multi-line <select and <input type={k}>, and it flagged both while passing a checkbox and <Input type="text">. npm run check:styles passes on the current HEAD, and ui-patterns.md:10-22 documents the rule.
Review notes
Good extra catches: Input and Select now forward native attributes and merge aria-describedby with the field's message (Input.svelte:46). Field no longer points aria-labelledby at a label that doesn't exist. Error text that used to sit inside some labels is now outside them and linked by aria-describedby. The #104 date-range fix (MatchHistory.svelte:186, ActivityLog.svelte:247,254) and the #115 link and running time still hold after this commit.
All 39 ui-* test files pass locally (308 tests). CI run 849 on this commit is green.
Minor and non-blocking: two help texts on the new beta page are now loose spans inside Field, so screen readers no longer announce them with their field (betas/new/+page.svelte:678-679,761-762). Before, they were inside the label. Field's hint prop would link them.
Minor and non-blocking: some page-scoped rules for text inputs no longer match anything, because the inner <input> now lives in Input and page styles can't reach it. For example, settings/users/+page.svelte:668 styles input:not([type='checkbox']), but that page only has checkboxes left.
Minor and non-blocking: a few controls look different now. Activity's "Per page" label sits above its select instead of beside it, because the .pagination label rule was removed. Settings' number fields are 100 px wide instead of 88 px and no longer use tabular figures (SettingsEditor.svelte:532-535).
Outside this issue: 6 files still render a raw <textarea>, although ui-patterns.md:10 says to use Textarea: AddHostDrawer, BetaRevisionDialog, BetaTestProgress, ConfigInspector, ConfigTextEditor and ScriptUploadDrawer. The check doesn't look at textareas.
Reviewed cc9c10226e81d0019b516a4f091b0a4df9e3ebd5. Every select and text-like input now goes through `Field` with `Select` or `Input`, and the style check rejects raw ones, so both fix items are done and I'm closing this.
**Checklist**
- [x] **Move raw text inputs and selects onto the shared components.** Before this commit, 35 files rendered a raw `<select>`, 42 a raw `<input>`, 7 used `Select` and 5 used `Input`, matching the issue. Now no file outside the primitives renders a raw `<select>`, and the only raw `<input>`s left are checkboxes, radios and file pickers. 41 files use `Select` and 40 use `Input`. A comparison of every old control with its replacement found the IDs, names, `autocomplete` values, `required`, length and pattern limits, ARIA attributes, bindings, handlers and option values unchanged, including on the login and setup forms. In the preview, every visible select on Leaderboard, Matches, Activity, Provisioning, Metrics and Capacity now has the shared ▾ (`Select.svelte:55,67`) instead of the browser's chevron.
- [x] **Make `check-ui-styles.mjs` flag raw controls.** `checkFormControls` parses each file's markup with the Svelte compiler and flags every `<select>`, and every `<input>` whose type is missing, dynamic or not in the native list at `check-ui-styles.mjs:6-17` (`check-ui-styles.mjs:23-69`). Only `primitives/Input.svelte` and `primitives/Select.svelte` are exempt (`check-ui-styles.mjs:26`). I ran it on a sample with a multi-line `<select` and `<input type={k}>`, and it flagged both while passing a checkbox and `<Input type="text">`. `npm run check:styles` passes on the current HEAD, and `ui-patterns.md:10-22` documents the rule.
**Review notes**
- Good extra catches: `Input` and `Select` now forward native attributes and merge `aria-describedby` with the field's message (`Input.svelte:46`). `Field` no longer points `aria-labelledby` at a label that doesn't exist. Error text that used to sit inside some labels is now outside them and linked by `aria-describedby`. The #104 date-range fix (`MatchHistory.svelte:186`, `ActivityLog.svelte:247,254`) and the #115 link and running time still hold after this commit.
- All 39 `ui-*` test files pass locally (308 tests). CI [run 849](https://gitea.thewrightserver.net/josh/electric-cherry/actions/runs/849) on this commit is green.
- Minor and non-blocking: two help texts on the new beta page are now loose spans inside `Field`, so screen readers no longer announce them with their field (`betas/new/+page.svelte:678-679,761-762`). Before, they were inside the label. Field's `hint` prop would link them.
- Minor and non-blocking: some page-scoped rules for text inputs no longer match anything, because the inner `<input>` now lives in `Input` and page styles can't reach it. For example, `settings/users/+page.svelte:668` styles `input:not([type='checkbox'])`, but that page only has checkboxes left.
- Minor and non-blocking: a few controls look different now. Activity's "Per page" label sits above its select instead of beside it, because the `.pagination label` rule was removed. Settings' number fields are 100 px wide instead of 88 px and no longer use tabular figures (`SettingsEditor.svelte:532-535`).
- Outside this issue: 6 files still render a raw `<textarea>`, although `ui-patterns.md:10` says to use `Textarea`: `AddHostDrawer`, `BetaRevisionDialog`, `BetaTestProgress`, `ConfigInspector`, `ConfigTextEditor` and `ScriptUploadDrawer`. The check doesn't look at textareas.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Most forms render raw
<select>and<input>elements with local styles instead of the shared components, so fields look different from page to page.Selectis used in 7 files while 35 render a raw<select>.Inputis used in 5 while 42 render a raw<input>. 46 files restyle fields locally.Select.svelte:57,69), while Leaderboard, Matches, Activity and Provisioning show the browser's chevron.ui-patterns.md:10says to useFieldwithInput,SelectorTextarea, butcheck-ui-styles.mjsonly checks font sizes (:13), so nothing catches raw controls.Screenshots
Before, Metrics uses the shared Select:

Before, Leaderboard uses a raw select with the browser chevron:

After (mockup), Leaderboard with the shared style:

Fix
FieldwithInputorSelectcheck-ui-styles.mjsto flag raw<select>and text<input>outside the primitivesReviewed
cc9c10226e. Every select and text-like input now goes throughFieldwithSelectorInput, and the style check rejects raw ones, so both fix items are done and I'm closing this.Checklist
<select>, 42 a raw<input>, 7 usedSelectand 5 usedInput, matching the issue. Now no file outside the primitives renders a raw<select>, and the only raw<input>s left are checkboxes, radios and file pickers. 41 files useSelectand 40 useInput. A comparison of every old control with its replacement found the IDs, names,autocompletevalues,required, length and pattern limits, ARIA attributes, bindings, handlers and option values unchanged, including on the login and setup forms. In the preview, every visible select on Leaderboard, Matches, Activity, Provisioning, Metrics and Capacity now has the shared ▾ (Select.svelte:55,67) instead of the browser's chevron.check-ui-styles.mjsflag raw controls.checkFormControlsparses each file's markup with the Svelte compiler and flags every<select>, and every<input>whose type is missing, dynamic or not in the native list atcheck-ui-styles.mjs:6-17(check-ui-styles.mjs:23-69). Onlyprimitives/Input.svelteandprimitives/Select.svelteare exempt (check-ui-styles.mjs:26). I ran it on a sample with a multi-line<selectand<input type={k}>, and it flagged both while passing a checkbox and<Input type="text">.npm run check:stylespasses on the current HEAD, andui-patterns.md:10-22documents the rule.Review notes
InputandSelectnow forward native attributes and mergearia-describedbywith the field's message (Input.svelte:46).Fieldno longer pointsaria-labelledbyat a label that doesn't exist. Error text that used to sit inside some labels is now outside them and linked byaria-describedby. The #104 date-range fix (MatchHistory.svelte:186,ActivityLog.svelte:247,254) and the #115 link and running time still hold after this commit.ui-*test files pass locally (308 tests). CI run 849 on this commit is green.Field, so screen readers no longer announce them with their field (betas/new/+page.svelte:678-679,761-762). Before, they were inside the label. Field'shintprop would link them.<input>now lives inInputand page styles can't reach it. For example,settings/users/+page.svelte:668stylesinput:not([type='checkbox']), but that page only has checkboxes left..pagination labelrule was removed. Settings' number fields are 100 px wide instead of 88 px and no longer use tabular figures (SettingsEditor.svelte:532-535).<textarea>, althoughui-patterns.md:10says to useTextarea:AddHostDrawer,BetaRevisionDialog,BetaTestProgress,ConfigInspector,ConfigTextEditorandScriptUploadDrawer. The check doesn't look at textareas.