refactor(web): replace raw button and input elements with Opal
Description
This is the second of four cleanup PRs on top of #16234, which is merged (stack: #16240 icons, #16241 raw elements, #16242 refresh components, #16243 Text). This PR replaces raw <button>, <input>, <select> and <textarea> elements with Opal components where Opal has a faithful replacement.
The result is small on purpose. Most of the value of this PR is the list of gaps it found. Of 58 raw-element sites in the baseline, only 2 have an Opal replacement that keeps the look and the behavior. The other 56 are kept, and each one is listed below with the reason. Most of them show a missing Opal component, not a careless raw element.
Changes
web/src/app/admin/bots/SlackBotUpdateForm.tsx: the native checkbox becomes OpalInputCheckbox. It is imported asOpalInputCheckbox, because the file already has a local helper namedInputCheckbox.onChangepasses straight through, because Opal forwards it to its hidden input, soe.target.checkedstill works.- Opal's checkbox takes no
className, so the oldme-2 my-autobecomesitems-center gap-2on the parentlabel. - Visual change: the box is 16px, not the old 14px native box.
web/src/app/ee/admin/performance/custom-analytics/CustomAnalyticsUpdateForm.tsx: the hand-styled password input becomesInputTypeIn type="password", with<Spacer rem={0.25} />in place ofmt-1. This matches the OpalInputTextAreathat the same form already uses.- I did not use
InputPasswordTypeIn, because it adds a show/hide toggle that the old field did not have.
- I did not use
Both files still have other violations, so they stay in the baseline.
Sites kept, grouped by the Opal gap they show
| Gap in Opal | Sites | Where |
|---|---|---|
Button takes only a string label. No button primitive takes rich children (icons, avatars, images, layout) | 25 | craft: CompactionMarker, CraftToolCard, ApprovalCard (2), ThinkingCard, TodoListCard, CraftToolGroup, TaskBody, ConnectDataBanner, BuildWelcome, AgentSwitcher, OpencodeDebugLogs. Other: SlackChannelConfigFormFields (agent chip), SourceTag, SettingsPage and nrf SettingsPanel (background tiles), documents/sets/page (aria-disabled keeps the tooltip working), ProjectFolderButton, LLMStep, CodeBlock, FileCard (2), ChatDocumentDisplay, FileTile, ScheduleEditor |
| No hidden file input opened by its own trigger | 7 | UserFilesModal, InlineFileManagement, AppearanceThemeSettings, UserLibraryModal, AgentEditorPage, CraftInputBar, FilePickerPopover |
No react-dropzone integration (getInputProps() owns the input) | 3 | SkillBundlePicker, SkillFilesPicker, ProjectContextPanel |
InputTextArea always has a border and a 48px minimum height. There is no borderless or auto-sizing variant | 4 | PasteTilePopover, HumanMessage (resizes itself through a ref), the standard-answer search box |
LinkButton/TextButton are text-03. There is no link-colored or action-colored variant. TextButton renders a <p>, so it cannot sit inside prose | 6 | RequestNewVerificationEmail, LoginPage, the 3 "view all" toggles in SlackChannelConfigFormFields, the inline citation in MemoizedTextComponents |
No segmented control, and SelectButton cannot match custom pills | 4 | ScheduleEditor weekday toggles, InstructionsDisplayModeToggle, SuggestedPrompts (2) |
No icon button without chrome (an icon-only Button adds a box and a hover background) | 3 | the X in SuggestedPrompts, the X in refresh Tag, the × in standard-answer |
| No inline rename input with no chrome | 1 | ButtonRenaming |
| No slider | 1 | SettingsPage range input |
| Hidden input passed through from props | 1 | table-layouts.tsx |
| refresh-components that are button primitives themselves | 3 | BigButton, SquareButton, refresh SelectButton. Their callers should migrate, not their internals |
Suggested follow-ups
These are the Opal additions that would unblock most of the remaining sites, in order of impact:
- A button primitive that takes rich children (
Interactivealready does the work inside Opal; app code needs a public wrapper). This unblocks 25 sites. - An upload trigger that wraps a hidden
<input type="file">and a dropzone-friendly input. This unblocks 10 sites. - A borderless, auto-sizing
InputTextAreavariant. This unblocks 4 sites. - Link-colored
LinkButton/TextButtonvariants and an inline (span) mode. This unblocks 6 sites. - A segmented control, an icon button without chrome, and a slider.
How Has This Been Tested?
- oxlint: the 2 changed files have no
react(forbid-elements)diagnostics. The full run has 0 errors. bun run types:check: no type errors.- pre-commit (oxfmt, oxlint) passes on the changed files.
Additional Options
- [Optional] Please cherry-pick this PR to the latest release version.
Summary by CodeRabbit
- Style
- The Slack bot settings checkbox and custom analytics password field now use the app’s standard form controls.
- The checkbox is vertically aligned with its label, uses consistent spacing, and retains its accessible label.
- Form Behavior
- The checkbox continues to reflect and update its checked state, and custom analytics form submission behavior is unchanged.