feat(checkbox): add error state via MD3 error tokens#4955
Conversation
|
Hey @fabriziocucci, thank you for your pull request 🤗. The documentation from this branch can be viewed here. |
f674da6 to
9e7f48c
Compare
9e7f48c to
ed8052a
Compare
There was a problem hiding this comment.
Pull request overview
Adds an error?: boolean prop to the core Checkbox components to support an MD3-style error visual state by sourcing colors from theme.colors.error (and intended onError for Android icon per PR description), implemented via shared selection-control color utilities.
Changes:
- Extended Checkbox selection-control color utilities to optionally return
theme.colors.errorfor checked/unchecked states. - Added
error?: booleantoCheckbox,CheckboxAndroid, andCheckboxIOSprops and forwarded it into the color utility functions. - Updated component prop documentation (JSDoc) to describe error-state precedence.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/components/Checkbox/utils.ts | Adds error handling to Android/iOS color helpers used by Checkbox (and shared selection-control helpers). |
| src/components/Checkbox/CheckboxAndroid.tsx | Adds error prop and forwards it into Android selection-control color computation. |
| src/components/Checkbox/CheckboxIOS.tsx | Adds error prop and forwards it into iOS checked color computation. |
| src/components/Checkbox/Checkbox.tsx | Adds error to the public Checkbox prop type/docs and forwards to platform implementations. |
Comments suppressed due to low confidence (1)
src/components/Checkbox/CheckboxAndroid.tsx:119
- The implementation currently sets
selectionControlColortotheme.colors.errorwhenerroris true, and that same value is used to tint the Android icon. This doesn’t match the stated behavior/spec of usingtheme.colors.onErrorfor the check/dash when the container iserror. If you want MD3 parity here, the icon color likely needs to be computed separately (e.g., container useserror, glyph usesonError) rather than using a singleselectionControlColorfor everything.
const { selectionControlColor, selectionControlOpacity } =
getAndroidSelectionControlColor({
theme,
disabled,
checked,
customColor: rest.color,
customUncheckedColor: rest.uncheckedColor,
error,
});
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Adds an `error?: boolean` prop to Checkbox, CheckboxAndroid and CheckboxIOS. When true, the outline (unchecked) and container (checked / indeterminate) use `theme.colors.error`. The `disabled` state and explicit `color` / `uncheckedColor` overrides take precedence. Addresses one bullet from callstack#4937 / callstack#4949 (Checkbox section, "Error state not implemented"). Verified visually on iOS Simulator and Android Emulator across light and dark themes.
ed8052a to
8c608fe
Compare
|
Re: the suppressed comment about Proper compositing (background Out of scope for this PR. |
|
cc @satya164 |
|
Some general comments:
Edit: I'm fine with handling these in other PRs. |
Per maintainer feedback on callstack#4955, replace the iOS-specific Checkbox implementation with the Android one (which is closer to the MD3 spec) and inline the unified implementation directly into Checkbox.tsx. Changes: - Checkbox.tsx: inline what was the Android implementation; render the same MD3-compliant control on both platforms. Drop the Platform.OS dispatching. - CheckboxAndroid.tsx, CheckboxIOS.tsx: deleted. - Checkbox/index.ts: `Checkbox.Android` and `Checkbox.IOS` are kept as back-compat aliases of `Checkbox` itself, so existing imports keep working with no breaking change. - CheckboxItem.tsx: drop the `mode`-based branching; always render `<Checkbox />`. The `mode` prop is kept and marked deprecated. - src/index.tsx: `CheckboxAndroidProps` and `CheckboxIOSProps` now alias the unified `Props` from Checkbox.tsx (no breaking change). - Snapshot tests updated: the renderer now uses the unified container (36x36 ripple) and MaterialCommunityIcon glyphs on both platforms. `getSelectionControlIOSColor` in utils.ts is intentionally left in place because RadioButtonIOS still depends on it. A similar unification for RadioButton can follow in a separate PR. As a side benefit, callers using `<Checkbox.IOS uncheckedColor=...>` will now see the prop applied (the old iOS variant silently dropped it), addressing one of the API-parity gaps noted in callstack#4949.
Per maintainer feedback on callstack#4955, replace the iOS-specific Checkbox implementation with the Android one (which is closer to the MD3 spec) and inline the unified implementation directly into Checkbox.tsx. Changes: - Checkbox.tsx: inline what was the Android implementation; render the same MD3-compliant control on both platforms. Drop the Platform.OS dispatching. - CheckboxAndroid.tsx, CheckboxIOS.tsx: deleted. - Checkbox/index.ts: `Checkbox.Android` and `Checkbox.IOS` are kept as back-compat aliases of `Checkbox` itself, so existing imports keep working with no breaking change. - CheckboxItem.tsx: drop the `mode`-based branching; always render `<Checkbox />`. The `mode` prop is kept and marked deprecated. - src/index.tsx: `CheckboxAndroidProps` and `CheckboxIOSProps` now alias the unified `Props` from Checkbox.tsx (no breaking change). - Snapshot tests updated: the renderer now uses the unified container (36x36 ripple) and MaterialCommunityIcon glyphs on both platforms. `getSelectionControlIOSColor` in utils.ts is intentionally left in place because RadioButtonIOS still depends on it. A similar unification for RadioButton can follow in a separate PR. As a side benefit, callers using `<Checkbox.IOS uncheckedColor=...>` will now see the prop applied (the old iOS variant silently dropped it), addressing one of the API-parity gaps noted in callstack#4949.
Per maintainer feedback on callstack#4955, replace the iOS-specific Checkbox implementation with the Android one (which is closer to the MD3 spec) and inline the unified implementation directly into Checkbox.tsx. Changes: - Checkbox.tsx: inline what was the Android implementation; render the same MD3-compliant control on both platforms. Drop the Platform.OS dispatching. - CheckboxAndroid.tsx, CheckboxIOS.tsx: deleted. - Checkbox/index.ts: `Checkbox.Android` and `Checkbox.IOS` are kept as back-compat aliases of `Checkbox` itself, so existing imports keep working with no breaking change. - CheckboxItem.tsx: drop the `mode`-based branching; always render `<Checkbox />`. The `mode` prop is kept and marked deprecated. - src/index.tsx: `CheckboxAndroidProps` and `CheckboxIOSProps` now alias the unified `Props` from Checkbox.tsx (no breaking change). - Snapshot tests updated: the renderer now uses the unified container (36x36 ripple) and MaterialCommunityIcon glyphs on both platforms. `getSelectionControlIOSColor` in utils.ts is intentionally left in place because RadioButtonIOS still depends on it. A similar unification for RadioButton can follow in a separate PR. As a side benefit, callers using `<Checkbox.IOS uncheckedColor=...>` will now see the prop applied (the old iOS variant silently dropped it), addressing one of the API-parity gaps noted in callstack#4949.
Per maintainer feedback on callstack#4955, replace the iOS-specific Checkbox implementation with the Android one (which is closer to the MD3 spec) and inline the unified implementation directly into Checkbox.tsx. Changes: - Checkbox.tsx: inline what was the Android implementation; render the same MD3-compliant control on both platforms. Drop the Platform.OS dispatching. - CheckboxAndroid.tsx, CheckboxIOS.tsx: deleted. - Checkbox/index.ts: `Checkbox.Android` and `Checkbox.IOS` are kept as back-compat aliases of `Checkbox` itself, so existing imports keep working with no breaking change. - CheckboxItem.tsx: drop the `mode`-based branching; always render `<Checkbox />`. The `mode` prop is kept and marked deprecated. - src/index.tsx: `CheckboxAndroidProps` and `CheckboxIOSProps` now alias the unified `Props` from Checkbox.tsx (no breaking change). - Snapshot tests updated: the renderer now uses the unified container (36x36 ripple) and MaterialCommunityIcon glyphs on both platforms. `getSelectionControlIOSColor` in utils.ts is intentionally left in place because RadioButtonIOS still depends on it. A similar unification for RadioButton can follow in a separate PR. As a side benefit, callers using `<Checkbox.IOS uncheckedColor=...>` will now see the prop applied (the old iOS variant silently dropped it), addressing one of the API-parity gaps noted in callstack#4949.
Per maintainer feedback on callstack#4955, drop the iOS-specific Checkbox renderer and inline the unified MD3 implementation directly into Checkbox.tsx. The same MaterialCommunityIcon-based control now renders on both platforms. Changes: - Checkbox.tsx: inlines what was the Android implementation; renders the same control on both platforms. Drops the Platform.OS dispatching. - CheckboxAndroid.tsx, CheckboxIOS.tsx: deleted. - Checkbox/index.ts: `Checkbox.Android` and `Checkbox.IOS` are kept as back-compat aliases of `Checkbox` itself, so existing imports keep working with no breaking change. - CheckboxItem.tsx: drops the `mode`-based branching; always renders `<Checkbox />`. The `mode` prop is kept and marked deprecated. - src/index.tsx: `CheckboxAndroidProps` and `CheckboxIOSProps` now alias the unified `Props` from Checkbox.tsx (no breaking change). - docs/docusaurus.config.js: removed entries for the now-deleted CheckboxAndroid and CheckboxIOS files. - Snapshot tests updated: the renderer now uses the unified container (36x36 ripple) and MaterialCommunityIcon glyphs on both platforms. `getSelectionControlIOSColor` in utils.ts is intentionally left in place because RadioButtonIOS still depends on it. A similar unification for RadioButton can follow in a separate PR. As a side benefit, callers using `<Checkbox.IOS uncheckedColor=...>` will now see the prop applied (the old iOS variant silently dropped it), addressing one of the API-parity gaps noted in callstack#4949.
Per maintainer feedback on callstack#4955, drop the iOS-specific Checkbox renderer and inline the unified MD3 implementation directly into Checkbox.tsx. The same MaterialCommunityIcon-based control now renders on both platforms. Changes: - Checkbox.tsx: inlines what was the Android implementation; renders the same control on both platforms. Drops the Platform.OS dispatching. - CheckboxAndroid.tsx, CheckboxIOS.tsx: deleted. - Checkbox/index.ts: `Checkbox.Android` and `Checkbox.IOS` are kept as back-compat aliases of `Checkbox` itself, so existing imports keep working with no breaking change. - CheckboxItem.tsx: drops the `mode`-based branching; always renders `<Checkbox />`. The `mode` prop is kept and marked deprecated. - src/index.tsx: `CheckboxAndroidProps` and `CheckboxIOSProps` now alias the unified `Props` from Checkbox.tsx (no breaking change). - docs/docusaurus.config.js: removed entries for the now-deleted CheckboxAndroid and CheckboxIOS files. - Snapshot tests updated: the renderer now uses the unified container (36x36 ripple) and MaterialCommunityIcon glyphs on both platforms. `getSelectionControlIOSColor` in utils.ts is intentionally left in place because RadioButtonIOS still depends on it. A similar unification for RadioButton can follow in a separate PR. As a side benefit, callers using `<Checkbox.IOS uncheckedColor=...>` will now see the prop applied (the old iOS variant silently dropped it), addressing one of the API-parity gaps noted in callstack#4949.
Per maintainer feedback on callstack#4955, drop the iOS-specific Checkbox renderer and inline the unified MD3 implementation directly into Checkbox.tsx. The same MaterialCommunityIcon-based control now renders on both platforms. Changes: - Checkbox.tsx: inlines what was the Android implementation; renders the same control on both platforms. Drops the Platform.OS dispatching. - CheckboxAndroid.tsx, CheckboxIOS.tsx: deleted. - Checkbox/index.ts: `Checkbox.Android` and `Checkbox.IOS` are kept as back-compat aliases of `Checkbox` itself, so existing imports keep working with no breaking change. - CheckboxItem.tsx: drops the `mode`-based branching; always renders `<Checkbox />`. The `mode` prop is kept and marked deprecated. - src/index.tsx: `CheckboxAndroidProps` and `CheckboxIOSProps` now alias the unified `Props` from Checkbox.tsx (no breaking change). - docs/docusaurus.config.js: removed entries for the now-deleted CheckboxAndroid and CheckboxIOS files. - Snapshot tests updated: the renderer now uses the unified container (36x36 ripple) and MaterialCommunityIcon glyphs on both platforms. `getSelectionControlIOSColor` in utils.ts is intentionally left in place because RadioButtonIOS still depends on it. A similar unification for RadioButton can follow in a separate PR. As a side benefit, callers using `<Checkbox.IOS uncheckedColor=...>` will now see the prop applied (the old iOS variant silently dropped it), addressing one of the API-parity gaps noted in callstack#4949.
Per maintainer feedback on callstack#4955, drop the iOS-specific Checkbox renderer and inline the unified MD3 implementation directly into Checkbox.tsx. The same MaterialCommunityIcon-based control now renders on both platforms. Changes: - Checkbox.tsx: inlines what was the Android implementation; renders the same control on both platforms. Drops the Platform.OS dispatching. - CheckboxAndroid.tsx, CheckboxIOS.tsx: deleted. - Checkbox/index.ts: `Checkbox.Android` and `Checkbox.IOS` are kept as back-compat aliases of `Checkbox` itself, so existing imports keep working with no breaking change. - CheckboxItem.tsx: drops the `mode`-based branching; always renders `<Checkbox />`. The `mode` prop is kept and marked deprecated. - src/index.tsx: `CheckboxAndroidProps` and `CheckboxIOSProps` now alias the unified `Props` from Checkbox.tsx (no breaking change). - docs/docusaurus.config.js: removed entries for the now-deleted CheckboxAndroid and CheckboxIOS files. - Snapshot tests updated: the renderer now uses the unified container (36x36 ripple) and MaterialCommunityIcon glyphs on both platforms. `getSelectionControlIOSColor` in utils.ts is intentionally left in place because RadioButtonIOS still depends on it. A similar unification for RadioButton can follow in a separate PR. As a side benefit, callers using `<Checkbox.IOS uncheckedColor=...>` will now see the prop applied (the old iOS variant silently dropped it), addressing one of the API-parity gaps noted in callstack#4949.
Per maintainer feedback on callstack#4955, drop the iOS-specific Checkbox renderer and inline the unified MD3 implementation directly into Checkbox.tsx. The same MaterialCommunityIcon-based control now renders on both platforms. Changes: - Checkbox.tsx: inlines what was the Android implementation; renders the same control on both platforms. Drops the Platform.OS dispatching. - CheckboxAndroid.tsx, CheckboxIOS.tsx: deleted. - Checkbox/index.ts: `Checkbox.Android` and `Checkbox.IOS` are kept as back-compat aliases of `Checkbox` itself, so existing imports keep working with no breaking change. - CheckboxItem.tsx: drops the `mode`-based branching; always renders `<Checkbox />`. The `mode` prop is kept and marked deprecated. - src/index.tsx: `CheckboxAndroidProps` and `CheckboxIOSProps` now alias the unified `Props` from Checkbox.tsx (no breaking change). - docs/docusaurus.config.js: removed entries for the now-deleted CheckboxAndroid and CheckboxIOS files. - Snapshot tests updated: the renderer now uses the unified container (36x36 ripple) and MaterialCommunityIcon glyphs on both platforms. `getSelectionControlIOSColor` in utils.ts is intentionally left in place because RadioButtonIOS still depends on it. A similar unification for RadioButton can follow in a separate PR. As a side benefit, callers using `<Checkbox.IOS uncheckedColor=...>` will now see the prop applied (the old iOS variant silently dropped it), addressing one of the API-parity gaps noted in callstack#4949.
Per maintainer feedback on callstack#4955, drop the iOS-specific Checkbox renderer and inline the unified MD3 implementation directly into Checkbox.tsx. The same MaterialCommunityIcon-based control now renders on both platforms. Changes: - Checkbox.tsx: inlines what was the Android implementation; renders the same control on both platforms. Drops the Platform.OS dispatching. - CheckboxAndroid.tsx, CheckboxIOS.tsx: deleted. - Checkbox/index.ts: `Checkbox.Android` and `Checkbox.IOS` are kept as back-compat aliases of `Checkbox` itself, so existing imports keep working with no breaking change. - CheckboxItem.tsx: drops the `mode`-based branching; always renders `<Checkbox />`. The `mode` prop is kept and marked deprecated. - src/index.tsx: `CheckboxAndroidProps` and `CheckboxIOSProps` now alias the unified `Props` from Checkbox.tsx (no breaking change). - docs/docusaurus.config.js: removed entries for the now-deleted CheckboxAndroid and CheckboxIOS files. - Snapshot tests updated: the renderer now uses the unified container (36x36 ripple) and MaterialCommunityIcon glyphs on both platforms. `getSelectionControlIOSColor` in utils.ts is intentionally left in place because RadioButtonIOS still depends on it. A similar unification for RadioButton can follow in a separate PR. As a side benefit, callers using `<Checkbox.IOS uncheckedColor=...>` will now see the prop applied (the old iOS variant silently dropped it), addressing one of the API-parity gaps noted in callstack#4949.
Per maintainer feedback on callstack#4955, drop the iOS-specific Checkbox renderer and inline the unified MD3 implementation directly into Checkbox.tsx. The same MaterialCommunityIcon-based control now renders on both platforms. Changes: - Checkbox.tsx: inlines what was the Android implementation; renders the same control on both platforms. Drops the Platform.OS dispatching. - CheckboxAndroid.tsx, CheckboxIOS.tsx: deleted. - Checkbox/index.ts: `Checkbox.Android` and `Checkbox.IOS` are kept as back-compat aliases of `Checkbox` itself, so existing imports keep working with no breaking change. - CheckboxItem.tsx: drops the `mode`-based branching; always renders `<Checkbox />`. The `mode` prop is kept and marked deprecated. - src/index.tsx: `CheckboxAndroidProps` and `CheckboxIOSProps` now alias the unified `Props` from Checkbox.tsx (no breaking change). - docs/docusaurus.config.js: removed entries for the now-deleted CheckboxAndroid and CheckboxIOS files. - Snapshot tests updated: the renderer now uses the unified container (36x36 ripple) and MaterialCommunityIcon glyphs on both platforms. `getSelectionControlIOSColor` in utils.ts is intentionally left in place because RadioButtonIOS still depends on it. A similar unification for RadioButton can follow in a separate PR. As a side benefit, callers using `<Checkbox.IOS uncheckedColor=...>` will now see the prop applied (the old iOS variant silently dropped it), addressing one of the API-parity gaps noted in callstack#4949.
Per maintainer feedback on #4955, drop the iOS-specific Checkbox renderer and inline the unified MD3 implementation directly into Checkbox.tsx. The same MaterialCommunityIcon-based control now renders on both platforms. Changes: - Checkbox.tsx: inlines what was the Android implementation; renders the same control on both platforms. Drops the Platform.OS dispatching. - CheckboxAndroid.tsx, CheckboxIOS.tsx: deleted. - Checkbox/index.ts: `Checkbox.Android` and `Checkbox.IOS` are kept as back-compat aliases of `Checkbox` itself, so existing imports keep working with no breaking change. - CheckboxItem.tsx: drops the `mode`-based branching; always renders `<Checkbox />`. The `mode` prop is kept and marked deprecated. - src/index.tsx: `CheckboxAndroidProps` and `CheckboxIOSProps` now alias the unified `Props` from Checkbox.tsx (no breaking change). - docs/docusaurus.config.js: removed entries for the now-deleted CheckboxAndroid and CheckboxIOS files. - Snapshot tests updated: the renderer now uses the unified container (36x36 ripple) and MaterialCommunityIcon glyphs on both platforms. `getSelectionControlIOSColor` in utils.ts is intentionally left in place because RadioButtonIOS still depends on it. A similar unification for RadioButton can follow in a separate PR. As a side benefit, callers using `<Checkbox.IOS uncheckedColor=...>` will now see the prop applied (the old iOS variant silently dropped it), addressing one of the API-parity gaps noted in #4949.
Summary
Adds an
error?: booleanprop toCheckbox,CheckboxAndroidandCheckboxIOS. Whentrue, the outline (unchecked) and container (checked / indeterminate) render withtheme.colors.error; the checked / indeterminate icon usestheme.colors.onErroron Android andtheme.colors.erroron iOS.This addresses one bullet from #4937 / discussion #4949 (Checkbox section, "Error state not implemented"):
MD3 spec reference: https://m3.material.io/components/checkbox/specs
Behavior
When
error={true}:theme.colors.errortheme.colors.errortheme.colors.errorComposition with existing props (precedence rules)
disabledwins: disabled tokens overrideerrorentirely. Matches MD3 spec (disabled is the highest-precedence visual state).colorwins for checked: an explicitcolorprop overrides theerrortoken for the checked container.errorvia the unchecked-color path on Android. It does NOT honor acoloroverride (pre-existing v6 behavior, not introduced by this PR). Tracked separately for a focused follow-up.uncheckedColorwins for unchecked outline only: an explicituncheckedColoroverrides theerrortoken for the unchecked outline; checked / indeterminate states still use theerrorcontainer.errorprop = no behavior change: the prop defaults toundefined, so existing usages are byte-for-byte identical.Implementation
src/components/Checkbox/utils.ts: extendedgetAndroidCheckedColor,getAndroidUncheckedColor,getAndroidSelectionControlColor,getIOSCheckedColorandgetSelectionControlIOSColorwith an optionalerrorparameter. The error branch sits betweencustom*(highest precedence) and the theme defaults (lowest), preserving existing behavior whenerroris unset.src/components/Checkbox/Checkbox.tsx: addederror?: booleantoPropswith JSDoc.src/components/Checkbox/CheckboxAndroid.tsx: addederrortoProps, destructured from the component's args, forwarded togetAndroidSelectionControlColor.src/components/Checkbox/CheckboxIOS.tsx: same pattern as Android.CheckboxItem.tsxis not modified in this PR. That's a follow-up.CheckboxItemwould just need to adderror?: booleanto itsPropsand forward to the embedded<Checkbox>. Splitting it keeps this PR focused on the core component.Testing
Visually verified on iOS Simulator (iPhone 17 Pro, iOS 18) and Android Emulator (Pixel 9, API 35) across light and dark themes using a standalone Expo app with the patch applied via
patch-package.Unit tests for the new error path added in
src/components/__tests__/Checkbox/utils.test.tsx(10 new cases on top of the existing 10):disabled-wins /customColor-wins /customUncheckedColor-winsdisabled-wins /customColor-winsScreenshots:
iOS
Android
A few observations:
#FFB4AB-ish) rather than the light-mode bright red (#B3261E). That's correct. Both come fromtheme.colors.error, which MD3 specifies separately for the two themes. The patch is token-based so any custom theme passed viaPaperProviderJust Works.getIOSCheckedColor; you'd see it once the unchecked box is added in a future PR.error + disabled,error + color,error + uncheckedColor) were each verified visually.What's NOT in this PR
These were considered and intentionally deferred (each could be its own PR):
theme.colors.errorContainersupport (would need an additionalcontainerColorprop / API change)react-native-paperper React Native Paper: MD3 Component Review #4949 General Notes)theme.shapes.fulletc., also React Native Paper: MD3 Component Review #4949 General Notes)Happy to discuss bundling any of these into this PR if you'd prefer, or to send follow-ups.
Conventional commit
feat(checkbox): add error state via MD3 error tokensRelated
cc @adrcotfas