Skip to content

feat(Checkbox/Switch): add support for trueValue / falseValue props#6150

Merged
benjamincanac merged 13 commits intov4from
pr/5821
Mar 8, 2026
Merged

feat(Checkbox/Switch): add support for trueValue / falseValue props#6150
benjamincanac merged 13 commits intov4from
pr/5821

Conversation

@benjamincanac
Copy link
Member

@benjamincanac benjamincanac commented Mar 4, 2026

🔗 Linked issue

Resolves #5821, follows-up #6147

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@github-actions github-actions bot added the v4 #4488 label Mar 4, 2026
@benjamincanac benjamincanac requested a review from sandros94 March 4, 2026 09:54
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 6, 2026

npm i https://pkg.pr.new/@nuxt/ui@6150

commit: 554e963

@benjamincanac benjamincanac marked this pull request as ready for review March 6, 2026 13:03
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6d386847-6c98-4a6f-8bed-5373791c7b7c

📥 Commits

Reviewing files that changed from the base of the PR and between c8ce2ae and 4e063dd.

📒 Files selected for processing (2)
  • test/components/Checkbox.spec.ts
  • test/components/Switch.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/components/Checkbox.spec.ts

📝 Walkthrough

Walkthrough

The PR makes Checkbox and Switch generic (script setup generic="T = boolean"), adding typed modelValue, trueValue, and falseValue to props and emits (CheckboxProps<T>, CheckboxEmits<T>, SwitchProps<T>, SwitchEmits<T>). useForwardProps is replaced with useForwardPropsEmits and wired to forward the v-model-related props and emits. useFormField, defineProps, and defineEmits usages were updated to use generics. Components no longer bind v-model to their root; state is updated via emitted update:model-value/change events. Tests were updated and new tests added for custom true/false values.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature addition: support for trueValue and falseValue props on Checkbox and Switch components.
Description check ✅ Passed The description links to the relevant issue (#5821) and follow-up PR (#6147), providing context for the changes despite lacking implementation details.
Linked Issues check ✅ Passed The implementation adds generic type parameters and trueValue/falseValue props to both Checkbox and Switch components, meeting the core requirement to support custom true/false values like native checkboxes.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing trueValue/falseValue support in Checkbox and Switch components with corresponding test coverage; no unrelated changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr/5821

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/runtime/components/Switch.vue (1)

99-105: Synthetic event's target property won't be set at runtime.

The Event constructor ignores the target property in EventInitevent.target will be null until the event is dispatched on a DOM element. If any consumer of the change event expects event.target.value, it will fail silently.

If the value needs to be accessible, consider using a CustomEvent with the value in detail:

💡 Optional improvement
 function onUpdate(value: any) {
-  // `@ts-expect-error` - 'target' does not exist in type 'EventInit'
-  const event = new Event('change', { target: { value } })
+  const event = new CustomEvent('change', { detail: { value } })
   emits('change', event)
   emitFormChange()
   emitFormInput()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Switch.vue` around lines 99 - 105, The synthetic Event
created in onUpdate (in Switch.vue) sets a non-existent target and will have
event.target === null at runtime, so replace the Event with a CustomEvent that
carries the new value in detail (e.g. new CustomEvent('change', { detail: {
value } })), then call emits('change', customEvent) and keep the existing
emitFormChange() and emitFormInput() calls; ensure any consumers expecting
event.target.value are updated to read event.detail.value instead or emit a
plain value alongside the event if you prefer backward compatibility.
src/runtime/components/Checkbox.vue (1)

103-109: Same synthetic event limitation as Switch.vue.

This has the same Event constructor limitation where target won't be set. Consider applying the same fix if you decide to use CustomEvent in Switch.vue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Checkbox.vue` around lines 103 - 109, The synthetic
Event created in onUpdate does not support a custom target property; replace the
Event with a CustomEvent (e.g., new CustomEvent('change', { detail: { value },
bubbles: true })) and update callers to read the value from event.detail.value
(or attach a small wrapper object if needed) before calling emits('change',
event), then call emitFormChange() and emitFormInput() as before; update any
downstream consumers expecting event.target.value to use event.detail.value (or
adapt the wrapper) so onUpdate, emits('change', emitFormChange, and
emitFormInput remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/runtime/components/Checkbox.vue`:
- Around line 103-109: The synthetic Event created in onUpdate does not support
a custom target property; replace the Event with a CustomEvent (e.g., new
CustomEvent('change', { detail: { value }, bubbles: true })) and update callers
to read the value from event.detail.value (or attach a small wrapper object if
needed) before calling emits('change', event), then call emitFormChange() and
emitFormInput() as before; update any downstream consumers expecting
event.target.value to use event.detail.value (or adapt the wrapper) so onUpdate,
emits('change', emitFormChange, and emitFormInput remain consistent.

In `@src/runtime/components/Switch.vue`:
- Around line 99-105: The synthetic Event created in onUpdate (in Switch.vue)
sets a non-existent target and will have event.target === null at runtime, so
replace the Event with a CustomEvent that carries the new value in detail (e.g.
new CustomEvent('change', { detail: { value } })), then call emits('change',
customEvent) and keep the existing emitFormChange() and emitFormInput() calls;
ensure any consumers expecting event.target.value are updated to read
event.detail.value instead or emit a plain value alongside the event if you
prefer backward compatibility.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: efe0cb2d-bd69-4098-9195-4ced7966c5a2

📥 Commits

Reviewing files that changed from the base of the PR and between ee8a248 and 8fd80d1.

📒 Files selected for processing (3)
  • src/runtime/components/Checkbox.vue
  • src/runtime/components/Switch.vue
  • test/components/Table.spec.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/components/Checkbox.vue`:
- Line 12: The prop typing for CheckboxProps currently picks 'modelValue' from
CheckboxRootProps but must use the normalized tri-state 'state' instead to
correctly detect indeterminate when users supply custom trueValue/falseValue;
update the Pick in CheckboxProps to include 'state' in place of 'modelValue'
(retain the other keys: 'disabled', 'required', 'name', 'value', 'id',
'defaultValue', 'trueValue', 'falseValue'), and ensure any usages of
CheckboxProps that relied on modelValue for indeterminate checks switch to the
CheckboxRoot slot's state ('false' | true | 'indeterminate') for tri-state
logic.
- Around line 53-55: The change event currently emits a plain Event that cannot
carry typed values for the generic model (CheckboxEmits -> change), so replace
the use of Event with a CustomEvent carrying the new value in its detail (e.g.,
CustomEvent<T | 'indeterminate'>) and update CheckboxEmits to declare change:
[event: CustomEvent<T | 'indeterminate'>]; ensure the component dispatches new
CustomEvent(...) with the toggled value in detail instead of attempting to write
to event.target, and propagate the same pattern to other form components that
currently use new Event('change', { target: { value } }) so consumers can read
typed payloads from event.detail.

In `@src/runtime/components/Switch.vue`:
- Around line 50-52: The `change` emit currently declares an Event payload but
synthetic Events can't carry a custom target/value; update SwitchEmits<T> so the
change event exposes the switch value directly (e.g., change: [value: T]) and
adjust emit sites in the component (where emit('change', ...), or any emitChange
helper) to pass the new value (or emit a CustomEvent with the value in detail)
instead of trying to construct an Event with a target. Also update any
listeners/tests/types expecting Event to use the new value payload.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6a341762-2472-403b-9728-97058747b9de

📥 Commits

Reviewing files that changed from the base of the PR and between 8fd80d1 and 212ff11.

📒 Files selected for processing (2)
  • src/runtime/components/Checkbox.vue
  • src/runtime/components/Switch.vue

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/runtime/components/Checkbox.vue (1)

53-55: ⚠️ Potential issue | 🟠 Major

change still drops the generic payload.

With trueValue / falseValue, consumers may need the new T from change, but Event cannot carry it here—EventInit has no target payload. If this event is meant to expose the toggled value, type it as CustomEvent<T | 'indeterminate'> and emit the value via detail instead.

Minimal shape
-export interface CheckboxEmits<T = boolean> extends CheckboxRootEmits<T> {
-  change: [event: Event]
+export interface CheckboxEmits<T = boolean> extends CheckboxRootEmits<T> {
+  change: [event: CustomEvent<T | 'indeterminate'>]
 }
function onUpdate(value: T | 'indeterminate') {
  emits('change', new CustomEvent('change', { detail: value }))
  emitFormChange()
  emitFormInput()
}
Does the DOM `Event` constructor accept a `target` field in `EventInit`, and is `CustomEvent.detail` the standard way to attach a typed payload?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Checkbox.vue` around lines 53 - 55, The current
CheckboxEmits<T> defines change as Event which loses the generic payload; change
should be typed as CustomEvent<T | 'indeterminate'> so consumers receive the
toggled value via detail. Update the interface entry `change: [event:
CustomEvent<T | 'indeterminate'>]` and adjust the emitter call (e.g., in the
`onUpdate`/emit site) to emit `new CustomEvent('change', { detail: value })`
rather than a plain Event so the typed payload is carried in `detail`. Ensure
any call sites and tests expecting Event are updated to read event.detail to
access the T or 'indeterminate' value.
🧹 Nitpick comments (1)
test/components/Checkbox.spec.ts (1)

71-96: Exercise the real toggle path here.

These assertions only prove that UCheckbox re-emits whatever CheckboxRoot already emitted. They would still pass if a click started emitting true/false again, or if unchecking stopped returning falseValue. Please add one interaction-based case that toggles both directions and asserts 'yes'/'no' or 1/0 end to end.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/components/Checkbox.spec.ts` around lines 71 - 96, Add an
interaction-based test that actually toggles the checkbox UI twice and asserts
end-to-end values instead of just re-emitting: mount Checkbox with custom
trueValue/falseValue and defaultValue, locate the real clickable element (the
CheckboxRoot component or its input), trigger a user interaction (e.g.,
element.trigger('click') or wrapper.find('input').trigger('click')) to check it
and assert the emitted 'update:modelValue' is the custom trueValue (and emitted
change event), then trigger a second click to uncheck and assert
'update:modelValue' is the custom falseValue (and emitted change event); update
the test suite alongside the existing tests that reference Checkbox and
CheckboxRoot.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/runtime/components/Checkbox.vue`:
- Around line 53-55: The current CheckboxEmits<T> defines change as Event which
loses the generic payload; change should be typed as CustomEvent<T |
'indeterminate'> so consumers receive the toggled value via detail. Update the
interface entry `change: [event: CustomEvent<T | 'indeterminate'>]` and adjust
the emitter call (e.g., in the `onUpdate`/emit site) to emit `new
CustomEvent('change', { detail: value })` rather than a plain Event so the typed
payload is carried in `detail`. Ensure any call sites and tests expecting Event
are updated to read event.detail to access the T or 'indeterminate' value.

---

Nitpick comments:
In `@test/components/Checkbox.spec.ts`:
- Around line 71-96: Add an interaction-based test that actually toggles the
checkbox UI twice and asserts end-to-end values instead of just re-emitting:
mount Checkbox with custom trueValue/falseValue and defaultValue, locate the
real clickable element (the CheckboxRoot component or its input), trigger a user
interaction (e.g., element.trigger('click') or
wrapper.find('input').trigger('click')) to check it and assert the emitted
'update:modelValue' is the custom trueValue (and emitted change event), then
trigger a second click to uncheck and assert 'update:modelValue' is the custom
falseValue (and emitted change event); update the test suite alongside the
existing tests that reference Checkbox and CheckboxRoot.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 073ccf13-7658-4aae-8fe9-8c8763f1e97d

📥 Commits

Reviewing files that changed from the base of the PR and between 212ff11 and fbcc219.

⛔ Files ignored due to path filters (2)
  • test/components/__snapshots__/Checkbox-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Checkbox.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • src/runtime/components/Checkbox.vue
  • test/components/Checkbox.spec.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/components/Switch.spec.ts`:
- Around line 70-95: The tests currently emit directly from SwitchRoot which
bypasses prop forwarding; update the three tests for Switch to assert that the
SwitchRoot component receives the trueValue/falseValue props (e.g., check
wrapper.findComponent({ name: 'SwitchRoot' }).props() includes
trueValue/falseValue) and in the "change event with custom trueValue/falseValue"
test also assert that the emitted change event payload includes the custom value
on event.target.value (i.e., after emitting update:modelValue use
wrapper.emitted('change') to verify the object contains target.value === the
custom trueValue/falseValue); ensure you reference the Switch component, the
SwitchRoot child, and the trueValue/falseValue props when adding these
assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d44c60e9-9cdf-42f0-97ec-4798a2977e73

📥 Commits

Reviewing files that changed from the base of the PR and between fbcc219 and c8ce2ae.

⛔ Files ignored due to path filters (2)
  • test/components/__snapshots__/Switch-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Switch.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (1)
  • test/components/Switch.spec.ts

@benjamincanac benjamincanac merged commit 91c6356 into v4 Mar 8, 2026
17 checks passed
@benjamincanac benjamincanac deleted the pr/5821 branch March 8, 2026 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UCheckbox/USwitch: add support for true/false custom values

2 participants