[Bug Fix] DataTablePerPageSelect: match shadcn's Select and submit without an inline onchange
Related issue
None opened, small self-contained fix.
Description
DataTablePerPageSelect submitted through an inline onchange="this.form.requestSubmit()", which a Content-Security-Policy without 'unsafe-inline' blocks, so changing the page size did nothing there. It now submits through a ruby-ui--data-table-per-page-select Stimulus controller.
After the shadcn audit it also renders as shadcn's dashboard-01 block does: RubyUI::Select with SelectTrigger size: :sm, class: "w-20" labelled by the <label for> and the list opening on top, instead of a NativeSelect. SelectInput carries name/value and the submit controller: picking a size fires change there and the form submits GET ?per_page=N (search and sort kept, page reset) into the Turbo Frame. The public API of the component is unchanged.
The server-driven submit is registered in audit/divergences/data_table.yml, and the component map's DataTablePerPageSelect selector now points at the trigger.
| Closed | Open |
|---|---|
![]() | ![]() |
![]() | ![]() |
shadcn audit · main
shadcn audit PR #579 ruby-ui/ruby_ui
base ce88825d head 43adc9a5 shadcn 8a353667
⚠️ Environment font: "Inter Variable", "system-ui", -apple-system, "system-ui", "Segoe UI", Roboto, "Helvetica Neue", Arial, "sans-serif" vs "Inter Variable", sans-serif (light)
⚠️ Environment font: "Inter Variable", "system-ui", -apple-system, "system-ui", "Segoe UI", Roboto, "Helvetica Neue", Arial, "sans-serif" vs "Inter Variable", sans-serif (dark)
✅ Theme neutral tokens match shadcn
DataTable (ref: bases/base/blocks/dashboard-01/components/data-table.tsx + style-vega.css @ 8a353667)
❌ Look display: inline-flex
vega: display: flex
DataTableColumnToggle — closed/light, open/light, focus/light, after-esc/light, closed/dark, open/dark, focus/dark, after-esc/dark
↳ computed styles (shadcn-audit collect)
❌ Look background-color: rgba(0, 0, 0, 0); border-bottom-color: oklch(1 0 0 / 0.1); border-left-color: oklch(1 0 0 / 0.1); border-right-color: oklch(1 0 0 / 0.1); border-top-color: oklch(1 0 0 / 0.1); box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, oklab(0 0 0 / 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px; column-gap: normal; display: inline-block; gap: normal; padding-bottom: 2px; padding-right: 32px; padding-top: 2px; row-gap: normal; text-align: start
vega: background-color: oklab(1 0 0 / 0.045); border-bottom-color: oklch(1 0 0 / 0.15); border-left-color: oklch(1 0 0 / 0.15); border-right-color: oklch(1 0 0 / 0.15); border-top-color: oklch(1 0 0 / 0.15); box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px; column-gap: 6px; display: flex; gap: 6px; padding-bottom: 8px; padding-right: 8px; padding-top: 8px; row-gap: 6px; text-align: center
DataTablePerPageSelect renders RubyUI::NativeSelect size :sm (h-7 py-0.5 pr-8 pl-2.5 bg-transparent border-border); the shadcn block renders SelectTrigger size=sm className=w-20 (h-8 gap-1.5 py-2 pr-2 pl-2.5 dark:bg-input/30 border-input). Pre-existing: this PR doesn't touch the select's classes. DataTablePerPageSelect — closed/light, open/light, focus/light, after-esc/light, closed/dark, open/dark, focus/dark, after-esc/dark
↳ computed styles (shadcn-audit collect)
❌ Look color: oklch(0.556 0 0); color: oklch(0.708 0 0); display: block; text-align: start
vega: color: oklch(0.145 0 0); color: oklch(0.985 0 0); display: flex; text-align: left
DataTableRowCheckbox — closed/light, open/light, focus/light, after-esc/light, closed/dark, open/dark, focus/dark, after-esc/dark
↳ computed styles (shadcn-audit collect)
⏸ Look properties: font-family, height, width
DataTableColumnToggle, DropdownMenuContent, DataTablePerPageSelect, DataTableRowCheckbox — explained by environment diff: font
⚠️ ARIA aria-expanded: (none); aria-haspopup: (none); snapshot: only here: - combobox "Rows per page": | - option "5" | - option "10" [selected] | - option "25" | - option "50"
shadcn: aria-expanded: false; aria-haspopup: listbox; snapshot: only here: - combobox "Rows per page": "10"
Native <select> exposes its options in the accessibility tree and has no aria-expanded/aria-haspopup; Base UI's trigger is a combobox with haspopup=listbox. Accessible name matches ("Rows per page", label[for]). Pre-existing. DataTablePerPageSelect — closed/light, open/light, focus/light, after-esc/light, closed/dark, open/dark, focus/dark, after-esc/dark
↳ accessibility tree (shadcn-audit collect)
✅ ARIA DataTableColumnToggle, DropdownMenuContent, DataTableRowCheckbox: accessibility tree matches
↳ accessibility tree (shadcn-audit collect)
⚠️ Behavior change on the select submits the form through ruby-ui--data-table-per-page-select#submit (form.requestSubmit) -> GET ?per_page=50; no inline handler in the DOM (0 on* attributes)
shadcn: onValueChange -> table.setPageSize, client-side, no request
Verified in the browser (fill 50 -> http://localhost:3179/__audit/data_table?per_page=50). Same outcome, server-driven, as the registered data_table HTML divergence explains for pagination.
↳ audit/fixtures/data_table/shadcn.tsx:147
✅ Native native <select> + <label for> (keyboard, typeahead, Esc and the picker come from the browser) — native
The only JS left is the auto-submit on change, which has no native equivalent. The Stimulus controller replaces the inline onchange, so it also works under a CSP without 'unsafe-inline'.
↳ https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select
⏸ Behavior DataTableColumnToggle menu set (arrows, typeahead, Esc focus return, outside click) not exercised in this run: outside this PR's change; it renders RubyUI::DropdownMenu, audited in #630.
⏸ Animation Not exercised: the native select picker is drawn by the browser; the ColumnToggle menu animation belongs to the DropdownMenu audit (#630).
DataTablePerPageSelect (not in the component map)
⏸ Look Not in the component map. False positive of `target`: the folder comes from docs/app/javascript/controllers/ruby_ui/data_table_per_page_select_controller.js; the component lives in gem/lib/ruby_ui/data_table/ and is audited under data_table.
shadcn audit · this PR
shadcn audit PR #579 ruby-ui/ruby_ui
base 1fd93db0 head 32b1f9a8 shadcn 8a353667
⚠️ Environment font: "Inter Variable", "system-ui", -apple-system, "system-ui", "Segoe UI", Roboto, "Helvetica Neue", Arial, "sans-serif" vs "Inter Variable", sans-serif (light)
⚠️ Environment font: "Inter Variable", "system-ui", -apple-system, "system-ui", "Segoe UI", Roboto, "Helvetica Neue", Arial, "sans-serif" vs "Inter Variable", sans-serif (dark)
✅ Theme neutral tokens match shadcn
DataTable (ref: bases/base/blocks/dashboard-01/components/data-table.tsx + style-vega.css @ 8a353667)
❌ Look display: inline-flex
vega: display: flex
Pre-existing, not touched by this PR; follow-up PR. DataTableColumnToggle — closed/light, open/light, focus/light, after-esc/light, closed/dark, open/dark, focus/dark, after-esc/dark
↳ computed styles (shadcn-audit collect)
❌ Look display: block; text-align: start
vega: display: flex; text-align: left
Pre-existing, not touched by this PR; follow-up PR. DataTableRowCheckbox — closed/light, open/light, focus/light, after-esc/light, closed/dark, open/dark, focus/dark, after-esc/dark
↳ computed styles (shadcn-audit collect)
⏸ Look properties: font-family
DataTableColumnToggle, DropdownMenuContent, DataTablePerPageSelect, DataTableRowCheckbox — explained by environment diff: font
✅ ARIA DataTableColumnToggle, DropdownMenuContent, DataTablePerPageSelect, DataTableRowCheckbox: accessibility tree matches
↳ accessibility tree (shadcn-audit collect)
✅ Look DataTablePerPageSelect: RubyUI::SelectTrigger size: :sm class: w-20 (h-8 gap-1.5 py-2 pr-2 pl-2.5 border-input dark:bg-input/30)
No computed-style difference left on the per-page trigger, light and dark (only the font environment diff).
↳ computed styles (shadcn-audit collect)
✅ Behavior click opens with focus on the selected option (10); typeahead 5 -> 50; Enter selects; Esc closes and returns focus to the trigger
Driven with real clicks/keys on both sides (chrome-devtools).
↳ bases/base/ui/select.tsx
✅ Behavior picking a size submits GET ?per_page=N (verified: Enter on 50 -> /__audit/data_table?per_page=50; docs demo frame goes 5 -> 10 rows); no inline handler in the DOM — intentional
RubyUI's DataTable is server-driven, as for pagination: the page size is a query param the server reads, so the list comes back from the server and the URL can be shared. The Select itself (open, arrows, typeahead, Enter, Esc) behaves as shadcn's.
↳ audit/divergences/data_table.yml
✅ Props SelectContent side: :top opens beside the trigger — intentional
Overlaying the selected item needs custom positioning beyond Floating UI offset/flip/shift; the beside-the-trigger placement is what shadcn falls back to when there is no room.
↳ audit/divergences/select.yml
✅ Native the only JS the component adds is the auto-submit on change, via a Stimulus controller on SelectInput
No native feature submits a form on change; requestSubmit keeps validation and submit events. Works under a CSP without 'unsafe-inline'.
↳ https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit
⏸ Behavior DataTableColumnToggle menu set (arrows, typeahead, Esc focus return, outside click, animation) not exercised here: outside this PR; follow-up PR.
The two ❌ left (Columns trigger and row checkbox display) are pre-existing and go to a separate PR, together with the menu behavior this audit didn't cover.
Testing instructions
cd gem && bundle exec rake→ 780 runs, 0 failures, StandardRB clean.cd mcp && bundle exec rake→ 33 runs, 0 failures./docs/data_table_demo: open "Rows per page" and pick 10 → the frame reloads with 10 rows.- Keyboard: focus the trigger, ArrowDown, type
5→ 50, Enter →?per_page=50. Esc closes it and focus returns to the trigger. - CSP: the rendered table has no
on*attribute.
🤖 Generated with Claude Code



