Skip to content

feat: add web platform support using CSS backdrop-filter#94

Open
Navipro70 wants to merge 5 commits intosbaiahmed1:mainfrom
Navipro70:feat/web-support
Open

feat: add web platform support using CSS backdrop-filter#94
Navipro70 wants to merge 5 commits intosbaiahmed1:mainfrom
Navipro70:feat/web-support

Conversation

@Navipro70
Copy link

@Navipro70 Navipro70 commented Feb 22, 2026

Summary

Adds web platform support for all library components using CSS backdrop-filter. This enables @sbaiahmed1/react-native-blur to work in React Native for Web projects without any additional configuration.

Closes #79

Approach

Each component gets a .web.tsx platform-specific file that Metro/webpack automatically resolves when bundling for web. A dedicated index.web.tsx entry point re-exports web components and type-only re-exports from native component definitions (erased at compile time).

Components implemented

Component Web implementation
BlurView CSS backdrop-filter: blur(Npx) + semi-transparent backgroundColor tint derived from blurType
VibrancyView Falls back to BlurView (same strategy as the Android fallback)
ProgressiveBlurView backdrop-filter + CSS mask-image with linear-gradient for directional blur
LiquidGlassView backdrop-filter: blur(40px) saturate(180%) with tint overlay for glass effect
LiquidGlassContainer Plain View with gap spacing (no native glass grouping API on web)
BlurSwitch React Native's built-in Switch component

BlurType mapping

All 21 BlurType values are mapped to appropriate rgba() background colours that approximate native iOS UIVisualEffectView tints:

  • Light variants (xlight, light, *MaterialLight) → white-based overlays with increasing opacity
  • Dark variants (dark, extraDark, *MaterialDark) → black-based overlays with increasing opacity
  • System materials → graduated opacity matching the iOS ultra-thin → chrome hierarchy

ProgressiveBlurView direction support

The direction prop maps to CSS mask-image gradients:

  • blurredTopClearBottomlinear-gradient(to bottom, black, transparent)
  • blurredBottomClearToplinear-gradient(to top, black, transparent)
  • blurredCenterClearTopAndBottom → gradient with transparent edges and opaque center

The startOffset prop controls where the gradient begins.

Files added

  • src/BlurView.web.tsx
  • src/VibrancyView.web.tsx
  • src/ProgressiveBlurView.web.tsx
  • src/LiquidGlassView.web.tsx
  • src/LiquidGlassContainer.web.tsx
  • src/BlurSwitch.web.tsx
  • src/index.web.tsx

Browser support

CSS backdrop-filter is supported in all modern browsers:

Notes

  • Zero new dependencies — only uses react-native and CSS
  • All props from native components are accepted (unused web-only props are silently ignored for API compatibility)
  • TypeScript strict mode compatible
  • blurAmount is clamped to 0–100 range matching the native behaviour

Summary by CodeRabbit

  • New Features
    • Web implementations added for blur/glass UI: BlurView, ProgressiveBlurView, VibrancyView, LiquidGlassView, LiquidGlassContainer, and a blur-styled Switch — all available via a unified web API surface.
  • Documentation
    • Platform and prop docs clarified with iOS/Web notes, added usage examples, browser compatibility guidance, and minor JSDoc refinements for props and defaults.

Implements web versions of all components using platform-specific
`.web.tsx` files that are automatically resolved by Metro/webpack
when bundling for web.

Components implemented:
- BlurView: CSS backdrop-filter with blurType → background tint mapping
- VibrancyView: Falls back to BlurView (same as Android)
- ProgressiveBlurView: backdrop-filter + CSS mask-image gradients
- LiquidGlassView: backdrop-filter with blur + saturate glass effect
- LiquidGlassContainer: Plain View with gap spacing
- BlurSwitch: React Native Switch component

All 21 BlurType values are mapped to appropriate rgba background
colours that approximate native iOS UIVisualEffectView tints.

Closes sbaiahmed1#79
@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Adds web-specific implementations and a web entrypoint for blur/glass UI components: BlurView, ProgressiveBlurView, LiquidGlassView, LiquidGlassContainer, VibrancyView, and BlurSwitch. Also centralizes web exports in src/index.web.tsx. Several native files received JSDoc/platform tag updates only.

Changes

Cohort / File(s) Summary
Web Blur core
src/BlurView.web.tsx, src/ProgressiveBlurView.web.tsx
New web implementations using CSS backdrop-filter/-webkit-backdrop-filter, clamping blurAmount, mapping blurType→tint overlays, supporting overlayColor, and rendering layered DOM when children exist. ProgressiveBlurView adds directional mask-gradient handling.
Liquid Glass
src/LiquidGlassView.web.tsx, src/LiquidGlassContainer.web.tsx
New web LiquidGlassView producing layered glass overlay (backdrop-filter + tint/opacity) and LiquidGlassContainer that forwards props and emulates spacing via CSS gap.
Vibrancy & Switch (web fallbacks)
src/VibrancyView.web.tsx, src/BlurSwitch.web.tsx
VibrancyView delegates to BlurView with defaults; BlurSwitch is a web fallback that wraps React Native Switch, exposing BlurSwitchProps (includes alias blurAmount unused on web).
Web entrypoint / types
src/index.web.tsx
New web index that re-exports web components and type-only bindings (BlurType, ProgressiveBlurDirection, GlassType, ValueChangeEvent) to form the public web API surface.
Docs / JSDoc only (native files)
src/BlurSwitch.tsx, src/LiquidGlassContainer.tsx, src/LiquidGlassView.tsx, src/ProgressiveBlurView.tsx, src/VibrancyView.tsx
Non-functional documentation changes: added/updated JSDoc @platform tags, usage examples, and minor comment edits; no API or runtime logic changes.
Repository docs
README.md
Expanded README with Web platform notes, browser compatibility, new component entries, and “What’s New” entry for LiquidGlassContainer.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

documentation

Suggested reviewers

  • sbaiahmed1

Poem

"🐰 I hopped through pixels, soft and bright,
I layered glass and tempered light.
With backdrop blur and tint in view,
I stitched web glass to mirror you.
Hop, merge, and let the UI take flight!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add web platform support using CSS backdrop-filter' directly and clearly summarizes the main change—introducing web platform support via CSS backdrop-filter implementation.
Linked Issues check ✅ Passed PR implements all requirements from #79: BlurView.web.tsx with CSS backdrop-filter, blurType-to-rgba mapping, blurAmount mapping, and extends support to other components (VibrancyView, ProgressiveBlurView, LiquidGlassView, LiquidGlassContainer, BlurSwitch) without introducing new dependencies.
Out of Scope Changes check ✅ Passed Minor documentation updates to JSDoc comments and a zIndex styling fix were added outside of #79's specific scope, but these are reasonable complementary improvements to the web support implementation.
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 unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@sbaiahmed1
Copy link
Owner

Hello @Navipro70
Thanks a lot for your contribution! We appreciate it 🙏

@DanielAraldi someone got before you 😜

Copy link

@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: 8

🧹 Nitpick comments (3)
src/VibrancyView.web.tsx (1)

20-36: Missing export default — inconsistent with every other .web.tsx file in this PR.

BlurView.web.tsx, ProgressiveBlurView.web.tsx, LiquidGlassView.web.tsx, and BlurSwitch.web.tsx all carry a export default alias. VibrancyView.web.tsx does not, which is inconsistent without a clear reason.

♻️ Proposed addition
  export const VibrancyView: React.FC<VibrancyViewProps> = ({ ... }) => { ... };
+
+export default VibrancyView;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VibrancyView.web.tsx` around lines 20 - 36, The file exports a named
component VibrancyView (with props VibrancyViewProps) but lacks a default export
like the other .web.tsx files; add a default export for consistency by exporting
VibrancyView as the module default (e.g., append or change to export default
VibrancyView) so consumers can import it the same way as BlurView.web.tsx,
ProgressiveBlurView.web.tsx, LiquidGlassView.web.tsx, and BlurSwitch.web.tsx.
src/LiquidGlassView.web.tsx (1)

25-25: Consider narrowing glassTintColor's type to make the 'clear' sentinel discoverable.

The default value 'clear' acts as an undocumented sentinel but the prop is typed as string, making it invisible to consumers via TypeScript. Consider glassTintColor?: 'clear' | (string & {}) so 'clear' autocompletes while still accepting arbitrary CSS color strings.

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

In `@src/LiquidGlassView.web.tsx` at line 25, The prop type for glassTintColor is
currently typed as string which hides the default sentinel 'clear'; update the
prop/interface/type declaration (where glassTintColor is defined, e.g., the
LiquidGlassView props type and any related PropTypes) to glassTintColor?:
'clear' | (string & {}) so that 'clear' is suggested by autocomplete while still
allowing arbitrary CSS color strings, and keep the default value glassTintColor
= 'clear' unchanged.
src/LiquidGlassContainer.web.tsx (1)

24-24: Optional: gap is a typed ViewStyle prop in React Native ≥ 0.71 — cast can be dropped.

The project targets React Native 0.79.2, which is well above RN 0.71 where FlexStyle added gap?: number. The as Record<string, unknown> cast is unnecessary and suppresses type safety on the entire object.

♻️ Proposed cleanup
-      style={[{ gap: spacing } as Record<string, unknown>, style]}
+      style={[{ gap: spacing }, style]}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/LiquidGlassContainer.web.tsx` at line 24, Remove the unnecessary cast on
the inline style object so the gap property keeps its proper typed
ViewStyle/FlexStyle type: in LiquidGlassContainer.web.tsx locate the JSX where
style is set (style={[{ gap: spacing } as Record<string, unknown>, style]}) and
replace it with the plain object form that includes gap (e.g., style={[{ gap:
spacing }, style]}) so TypeScript uses the correct RN FlexStyle gap typing
instead of erasing types with Record<string, unknown>.
🤖 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/BlurView.web.tsx`:
- Around line 85-92: In the BlurView no-children branch (the if that checks
Children.count(children)), the style array currently places blurStyle after
overlay so blurStyle.backgroundColor overrides the caller-supplied
overlay.backgroundColor; change the render to ensure the caller overlayColor
wins by either swapping the order so overlay is last in the style array or by
merging overlay.backgroundColor into blurStyle (preserving other blurStyle
properties) before applying styles; update the branch that returns <View
style={[style, overlay, blurStyle as Record<string, unknown>]} {...props} /> so
overlayColor is not silently ignored.
- Around line 97-100: Remove the ad-hoc cast in the JSX array and properly type
the blur style: replace style={[StyleSheet.absoluteFill, blurStyle as
Record<string, unknown>]} with style={[StyleSheet.absoluteFill, blurStyle]} and
change the declaration of blurStyle to a proper React Native style type (e.g.,
StyleProp<ViewStyle> / ViewStyle) in the BlurView component (or file) so no
inline cast is needed; alternatively, if a cast is required, use blurStyle as
ViewStyle instead of Record<string, unknown> to satisfy ESLint/Prettier.

In `@src/index.web.tsx`:
- Around line 5-7: Prettier/ESLint flags the multi-line type export for
ProgressiveBlurDirection; change the export to a single-line form without the
trailing comma so it formats correctly — update the export of
ProgressiveBlurDirection from ReactNativeProgressiveBlurViewNativeComponent to a
compact one-line export (replace the current multi-line block surrounding
ProgressiveBlurDirection).

In `@src/LiquidGlassView.web.tsx`:
- Around line 36-46: The tint color computation double-applies glassOpacity when
glassTintColor === 'clear', causing quadratic alpha; update the 'clear' branch
in the tintColor calculation so the alpha is the fixed base 0.15 (not 0.15 *
glassOpacity) and let glassStyle.opacity = glassOpacity uniformly control
overall transparency; locate the tintColor and glassStyle definitions (the
tintColor ternary and the glassStyle object) and remove the pre-multiplication
so the rgba uses 0.15 alpha and opacity remains glassOpacity.

In `@src/ProgressiveBlurView.web.tsx`:
- Around line 114-117: The array passed to the style prop combines
StyleSheet.absoluteFill and blurStyle with an unnecessary type cast that
triggers Prettier/ESLint formatting errors; update the style assignment in
ProgressiveBlurView.web.tsx to pass a single merged object or an array without
the "as Record<string, unknown>" cast (e.g., merge StyleSheet.absoluteFill and
blurStyle into one object or ensure blurStyle has the correct type), keeping the
reference to StyleSheet.absoluteFill and blurStyle so the linter no longer flags
formatting/type-cast issues.
- Around line 111-121: ProgressiveBlurView's render places children directly in
the root container, which can sit beneath the absolute-fill blur on some web
stacking contexts; update the StyleSheet.create block used by
ProgressiveBlurView.web.tsx to add a children style with zIndex: 1 (matching
BlurView's pattern) and then wrap the {children} return branch in a <View
style={styles.children}> so children are always painted above the blur layer;
update references to styles.children where the component returns its JSX
(component: ProgressiveBlurView) and ensure the new style is included in the
exported styles object.
- Around line 21-43: The duplicate blurTypeToBackground constant should be
extracted into a shared module and imported by both consumers: create a new
module (e.g., blurTypeUtils.web.ts) that exports blurTypeToBackground (using the
BlurType type from ReactNativeBlurViewNativeComponent), move the full
Record<BlurType, string> mapping there, then remove the local
blurTypeToBackground definitions from ProgressiveBlurView.web.tsx and
BlurView.web.tsx and replace them with an import like `import {
blurTypeToBackground } from './blurTypeUtils.web'`; ensure the new module
exports the same symbol name and that both files import the BlurType type if
needed for typings.
- Around line 50-65: The getMaskGradient function has an inversion bug for the
'blurredCenterClearTopAndBottom' case because start and end are computed once
and can produce start> end; update the switch so that the
'blurredCenterClearTopAndBottom' branch computes its own block-scoped
clampedOffset = Math.max(0, Math.min(startOffset, 0.5)), then compute start =
`${Math.round(clampedOffset * 100)}%` and end = `${100 -
Math.round(clampedOffset * 100)}%` before returning the gradient string; also
add a default case that returns a safe fallback (e.g., empty string) to handle
unknown ProgressiveBlurDirection values.

---

Nitpick comments:
In `@src/LiquidGlassContainer.web.tsx`:
- Line 24: Remove the unnecessary cast on the inline style object so the gap
property keeps its proper typed ViewStyle/FlexStyle type: in
LiquidGlassContainer.web.tsx locate the JSX where style is set (style={[{ gap:
spacing } as Record<string, unknown>, style]}) and replace it with the plain
object form that includes gap (e.g., style={[{ gap: spacing }, style]}) so
TypeScript uses the correct RN FlexStyle gap typing instead of erasing types
with Record<string, unknown>.

In `@src/LiquidGlassView.web.tsx`:
- Line 25: The prop type for glassTintColor is currently typed as string which
hides the default sentinel 'clear'; update the prop/interface/type declaration
(where glassTintColor is defined, e.g., the LiquidGlassView props type and any
related PropTypes) to glassTintColor?: 'clear' | (string & {}) so that 'clear'
is suggested by autocomplete while still allowing arbitrary CSS color strings,
and keep the default value glassTintColor = 'clear' unchanged.

In `@src/VibrancyView.web.tsx`:
- Around line 20-36: The file exports a named component VibrancyView (with props
VibrancyViewProps) but lacks a default export like the other .web.tsx files; add
a default export for consistency by exporting VibrancyView as the module default
(e.g., append or change to export default VibrancyView) so consumers can import
it the same way as BlurView.web.tsx, ProgressiveBlurView.web.tsx,
LiquidGlassView.web.tsx, and BlurSwitch.web.tsx.

@DanielAraldi DanielAraldi added enhancement New feature or request web Web only labels Feb 22, 2026
@DanielAraldi
Copy link
Collaborator

Hey @Navipro70, thank you so much for helping us with web support, thank you very much!! I will test this and merge it as soon as possible 🫡❤️

@DanielAraldi DanielAraldi self-requested a review February 23, 2026 10:57
Copy link
Collaborator

@DanielAraldi DanielAraldi left a comment

Choose a reason for hiding this comment

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

Bro, code is perfect! 👌

But could you make these changes and add web support in the README (with each specification about browser support)?

@sbaiahmed1
Copy link
Owner

Hey @DanielAraldi did you test it ? Still working on all plateforms ? ( for easy testing try expo )

Copy link

@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 (3)
src/ProgressiveBlurView.web.tsx (1)

107-122: ⚠️ Potential issue | 🟠 Major

Center-direction gradient can invert/collapse for larger startOffset.

In blurredCenterClearTopAndBottom, startOffset is not clamped, so values above 0.5 produce start > end and break the intended center mask shape. Also, adding a default fallback avoids undefined returns.

🐛 Proposed fix
 const getMaskGradient = (
   direction: ProgressiveBlurDirection,
   startOffset: number
 ): string => {
-  const start = `${Math.round(startOffset * 100)}%`;
-  const end = `${100 - Math.round(startOffset * 100)}%`;
+  const clamped = Math.max(0, Math.min(startOffset, 1));
+  const start = `${Math.round(clamped * 100)}%`;
+  const end = `${100 - Math.round(clamped * 100)}%`;

   switch (direction) {
     case 'blurredTopClearBottom':
       return `linear-gradient(to bottom, black ${start}, transparent 100%)`;
     case 'blurredBottomClearTop':
       return `linear-gradient(to top, black ${start}, transparent 100%)`;
-    case 'blurredCenterClearTopAndBottom':
-      return `linear-gradient(to bottom, transparent 0%, black ${start}, black ${end}, transparent 100%)`;
+    case 'blurredCenterClearTopAndBottom': {
+      const centerOffset = Math.max(0, Math.min(clamped, 0.5));
+      const centerStart = `${Math.round(centerOffset * 100)}%`;
+      const centerEnd = `${100 - Math.round(centerOffset * 100)}%`;
+      return `linear-gradient(to bottom, transparent 0%, black ${centerStart}, black ${centerEnd}, transparent 100%)`;
+    }
+    default:
+      return 'linear-gradient(to bottom, black 0%, transparent 100%)';
   }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProgressiveBlurView.web.tsx` around lines 107 - 122, The getMaskGradient
function can return invalid gradients when startOffset > 0.5 (start > end) and
may return undefined for unknown directions; clamp startOffset to the [0, 0.5]
range before computing start/end so the center mask never inverts (adjust
calculations used in the 'blurredCenterClearTopAndBottom' case), and add a
default/fallback return (e.g., a no-op or single-direction gradient) to ensure
getMaskGradient always returns a string; update references in the
getMaskGradient function and the 'blurredCenterClearTopAndBottom' branch
accordingly.
src/BlurView.web.tsx (1)

145-147: ⚠️ Potential issue | 🟡 Minor

overlayColor is overridden when children is absent.

On Line 146, [style, overlay, blurStyle] makes blurStyle.backgroundColor win, so overlayColor is silently ignored in this branch.

🐛 Proposed fix
-    return <View style={[style, overlay, blurStyle]} {...props} />;
+    return <View style={[style, blurStyle, overlay]} {...props} />;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/BlurView.web.tsx` around lines 145 - 147, When children are absent the
View is rendered with styles in the order [style, overlay, blurStyle], causing
blurStyle.backgroundColor to override overlayColor; fix by ensuring overlay
styles are applied last so overlayColor wins (e.g., change the array order to
[style, blurStyle, overlay] or merge overlay into the final style object) in the
branch that returns <View ... />; look for Children.count(children) and the
returned View using style, overlay, blurStyle to update the ordering so
overlayColor is not silently ignored.
src/LiquidGlassView.web.tsx (1)

120-130: ⚠️ Potential issue | 🟠 Major

glassOpacity is double-applied in the 'clear' tint branch.

Line 122 pre-multiplies alpha by glassOpacity, then Line 129 applies opacity: glassOpacity again. This makes the clear tint fade quadratically.

🐛 Proposed fix
   const tintColor =
     glassTintColor === 'clear'
-      ? `rgba(255, 255, 255, ${0.15 * glassOpacity})`
+      ? 'rgba(255, 255, 255, 0.15)'
       : glassTintColor;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/LiquidGlassView.web.tsx` around lines 120 - 130, The clear-tint branch
multiplies alpha by glassOpacity and then glassOpacity is applied again via
glassStyle.opacity, causing quadratic fading; change the 'clear' branch in
tintColor so it uses a fixed alpha (e.g. 0.15) instead of pre-multiplying by
glassOpacity (keep glassStyle.opacity: glassOpacity), i.e. update the
computation that sets tintColor (and thus backgroundColor) so it no longer
multiplies by glassOpacity; refer to tintColor, glassTintColor, glassOpacity,
and glassStyle.backgroundColor/opacity when making the change.
🧹 Nitpick comments (1)
src/BlurSwitch.tsx (1)

32-33: Inconsistent platform tag casing.

The @platform tag on line 32 is now capitalized to "Android", but line 41 still uses lowercase "ios". For consistency, consider using proper casing for both platforms: "Android" and "iOS".

📝 Suggested fix for consistency
   * `@description` The color of the switch thumb
   *
-   * `@platform` ios
+   * `@platform` iOS
   * `@default` '#FFFFFF'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/BlurSwitch.tsx` around lines 32 - 33, The JSDoc platform tags in
BlurSwitch.tsx are inconsistent (one uses "Android", another "ios"); update the
`@platform` tags in the component's JSDoc to use consistent casing "Android" and
"iOS" (capitalize the I) so both tags match; locate the JSDoc block above the
BlurSwitch component or export and change "ios" to "iOS".
🤖 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/BlurView.web.tsx`:
- Around line 145-147: When children are absent the View is rendered with styles
in the order [style, overlay, blurStyle], causing blurStyle.backgroundColor to
override overlayColor; fix by ensuring overlay styles are applied last so
overlayColor wins (e.g., change the array order to [style, blurStyle, overlay]
or merge overlay into the final style object) in the branch that returns <View
... />; look for Children.count(children) and the returned View using style,
overlay, blurStyle to update the ordering so overlayColor is not silently
ignored.

In `@src/LiquidGlassView.web.tsx`:
- Around line 120-130: The clear-tint branch multiplies alpha by glassOpacity
and then glassOpacity is applied again via glassStyle.opacity, causing quadratic
fading; change the 'clear' branch in tintColor so it uses a fixed alpha (e.g.
0.15) instead of pre-multiplying by glassOpacity (keep glassStyle.opacity:
glassOpacity), i.e. update the computation that sets tintColor (and thus
backgroundColor) so it no longer multiplies by glassOpacity; refer to tintColor,
glassTintColor, glassOpacity, and glassStyle.backgroundColor/opacity when making
the change.

In `@src/ProgressiveBlurView.web.tsx`:
- Around line 107-122: The getMaskGradient function can return invalid gradients
when startOffset > 0.5 (start > end) and may return undefined for unknown
directions; clamp startOffset to the [0, 0.5] range before computing start/end
so the center mask never inverts (adjust calculations used in the
'blurredCenterClearTopAndBottom' case), and add a default/fallback return (e.g.,
a no-op or single-direction gradient) to ensure getMaskGradient always returns a
string; update references in the getMaskGradient function and the
'blurredCenterClearTopAndBottom' branch accordingly.

---

Nitpick comments:
In `@src/BlurSwitch.tsx`:
- Around line 32-33: The JSDoc platform tags in BlurSwitch.tsx are inconsistent
(one uses "Android", another "ios"); update the `@platform` tags in the
component's JSDoc to use consistent casing "Android" and "iOS" (capitalize the
I) so both tags match; locate the JSDoc block above the BlurSwitch component or
export and change "ios" to "iOS".

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed54033 and 98116cc.

📒 Files selected for processing (10)
  • src/BlurSwitch.tsx
  • src/BlurSwitch.web.tsx
  • src/BlurView.web.tsx
  • src/LiquidGlassContainer.tsx
  • src/LiquidGlassContainer.web.tsx
  • src/LiquidGlassView.tsx
  • src/LiquidGlassView.web.tsx
  • src/ProgressiveBlurView.tsx
  • src/ProgressiveBlurView.web.tsx
  • src/index.web.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/ProgressiveBlurView.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/LiquidGlassContainer.web.tsx
  • src/index.web.tsx
  • src/BlurSwitch.web.tsx

@DanielAraldi
Copy link
Collaborator

Hey @DanielAraldi did you test it ? Still working on all plateforms ? ( for easy testing try expo )

I'm testing now haha

Copy link

@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

♻️ Duplicate comments (2)
src/ProgressiveBlurView.web.tsx (1)

107-122: ⚠️ Potential issue | 🟠 Major

blurredCenterClearTopAndBottom can generate inverted stops for high startOffset.

For values above 0.5, start > end, which collapses the center band. Clamp center offset to [0, 0.5] and add a safe default branch.

Suggested fix
 const getMaskGradient = (
   direction: ProgressiveBlurDirection,
   startOffset: number
 ): string => {
-  const start = `${Math.round(startOffset * 100)}%`;
-  const end = `${100 - Math.round(startOffset * 100)}%`;
+  const clamped = Math.max(0, Math.min(startOffset, 1));
+  const start = `${Math.round(clamped * 100)}%`;
+  const end = `${100 - Math.round(clamped * 100)}%`;

   switch (direction) {
     case 'blurredTopClearBottom':
       return `linear-gradient(to bottom, black ${start}, transparent 100%)`;
     case 'blurredBottomClearTop':
       return `linear-gradient(to top, black ${start}, transparent 100%)`;
-    case 'blurredCenterClearTopAndBottom':
-      return `linear-gradient(to bottom, transparent 0%, black ${start}, black ${end}, transparent 100%)`;
+    case 'blurredCenterClearTopAndBottom': {
+      const centerOffset = Math.max(0, Math.min(clamped, 0.5));
+      const cStart = `${Math.round(centerOffset * 100)}%`;
+      const cEnd = `${100 - Math.round(centerOffset * 100)}%`;
+      return `linear-gradient(to bottom, transparent 0%, black ${cStart}, black ${cEnd}, transparent 100%)`;
+    }
+    default:
+      return `linear-gradient(to bottom, black ${start}, transparent 100%)`;
   }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProgressiveBlurView.web.tsx` around lines 107 - 122, getMaskGradient can
produce inverted/overlapping gradient stops when startOffset > 0.5; clamp the
center offset to the range [0, 0.5] before computing percentages and compute
start/end from that clamped value (e.g., let c = Math.min(Math.max(startOffset,
0), 0.5); start = `${Math.round(c*100)}%`; end = `${100 - Math.round(c*100)}%`),
update the 'blurredCenterClearTopAndBottom' branch to use those clamped values,
and add a safe default return for unknown ProgressiveBlurDirection values so the
function always returns a valid gradient string (reference: getMaskGradient and
ProgressiveBlurDirection).
src/BlurView.web.tsx (1)

146-148: ⚠️ Potential issue | 🟡 Minor

overlayColor is still overridden when children is empty.

On Line 147, blurStyle.backgroundColor wins because it comes after overlay in the style array, so caller-provided overlayColor is effectively ignored in this branch.

Suggested fix
-    return <View style={[style, overlay, blurStyle]} {...props} />;
+    return <View style={[style, blurStyle, overlay]} {...props} />;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/BlurView.web.tsx` around lines 146 - 148, The branch that returns <View
style={[style, overlay, blurStyle]} {...props} /> overrides caller overlayColor
because blurStyle.bgColor comes after overlay; update the style order or merge
overlayColor into the last style so caller overlayColor wins — specifically in
the empty-children branch (the code using Children.count(children) and returning
View), change the style array to put blurStyle before overlay (e.g. [style,
blurStyle, overlay]) or ensure overlay.backgroundColor is assigned from the
overlayColor prop and used as the last item so overlayColor is not overwritten.
🤖 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/ProgressiveBlurView.web.tsx`:
- Around line 188-190: In the no-children render path the style array order
causes blurStyle.backgroundColor to override the passed-in overlayColor; in the
conditional branch that returns <View ... /> when Children.count(children) is
zero, reorder the style array so overlay is last (e.g., change [style, overlay,
blurStyle] to [style, blurStyle, overlay]) so the overlay prop wins; update the
return in ProgressiveBlurView.web.tsx where Children.count(children) is checked
and ensure the same ordering convention is used consistently.

---

Duplicate comments:
In `@src/BlurView.web.tsx`:
- Around line 146-148: The branch that returns <View style={[style, overlay,
blurStyle]} {...props} /> overrides caller overlayColor because
blurStyle.bgColor comes after overlay; update the style order or merge
overlayColor into the last style so caller overlayColor wins — specifically in
the empty-children branch (the code using Children.count(children) and returning
View), change the style array to put blurStyle before overlay (e.g. [style,
blurStyle, overlay]) or ensure overlay.backgroundColor is assigned from the
overlayColor prop and used as the last item so overlayColor is not overwritten.

In `@src/ProgressiveBlurView.web.tsx`:
- Around line 107-122: getMaskGradient can produce inverted/overlapping gradient
stops when startOffset > 0.5; clamp the center offset to the range [0, 0.5]
before computing percentages and compute start/end from that clamped value
(e.g., let c = Math.min(Math.max(startOffset, 0), 0.5); start =
`${Math.round(c*100)}%`; end = `${100 - Math.round(c*100)}%`), update the
'blurredCenterClearTopAndBottom' branch to use those clamped values, and add a
safe default return for unknown ProgressiveBlurDirection values so the function
always returns a valid gradient string (reference: getMaskGradient and
ProgressiveBlurDirection).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58db46f and cab58f4.

📒 Files selected for processing (6)
  • src/BlurSwitch.web.tsx
  • src/BlurView.web.tsx
  • src/LiquidGlassContainer.web.tsx
  • src/LiquidGlassView.web.tsx
  • src/ProgressiveBlurView.web.tsx
  • src/VibrancyView.web.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/LiquidGlassContainer.web.tsx
  • src/LiquidGlassView.web.tsx
  • src/VibrancyView.web.tsx
  • src/BlurSwitch.web.tsx

@DanielAraldi
Copy link
Collaborator

Hey @sbaiahmed1, the components are working, but this error occurs when they are imported:

Metro error: Importing native-only module "react-native/Libraries/Utilities/codegenNativeComponent" on web from: node_modules/@sbaiahmed1/react-native-blur/lib/module/ReactNativeBlurViewNativeComponent.ts

Import stack:

 node_modules/@sbaiahmed1/react-native-blur/lib/module/ReactNativeBlurViewNativeComponent.ts
 | import "react-native/Libraries/Utilities/codegenNativeComponent"
           ^ Importing react-native internals is not supported on web.

 node_modules/@sbaiahmed1/react-native-blur/lib/module/index.js
 | import "./ReactNativeBlurViewNativeComponent"

 app/(tabs)/index.tsx
 | import "@sbaiahmed1/react-native-blur"

Note: They only work without this error when I import them directly from the lib folder. But even so, it still shows an error during import (it turns red):

import { BlurView } from '@react-native-blur/lib/module/BlurView.web';

I'm trying to figure out how to solve this: Metro error: Importing native-only module "react-native/Libraries/Utilities/codegenNativeComponent".

@DanielAraldi
Copy link
Collaborator

Hey @sbaiahmed1, the components are working, but this error occurs when they are imported:

Metro error: Importing native-only module "react-native/Libraries/Utilities/codegenNativeComponent" on web from: node_modules/@sbaiahmed1/react-native-blur/lib/module/ReactNativeBlurViewNativeComponent.ts

Import stack:

 node_modules/@sbaiahmed1/react-native-blur/lib/module/ReactNativeBlurViewNativeComponent.ts
 | import "react-native/Libraries/Utilities/codegenNativeComponent"
           ^ Importing react-native internals is not supported on web.

 node_modules/@sbaiahmed1/react-native-blur/lib/module/index.js
 | import "./ReactNativeBlurViewNativeComponent"

 app/(tabs)/index.tsx
 | import "@sbaiahmed1/react-native-blur"

Note: They only work without this error when I import them directly from the lib folder. But even so, it still shows an error during import (it turns red):

import { BlurView } from '@react-native-blur/lib/module/BlurView.web';

I'm trying to figure out how to solve this: Metro error: Importing native-only module "react-native/Libraries/Utilities/codegenNativeComponent".

Hey @sbaiahmed1, I'm still trying fix this error. Basically, it occurs because *NativeComponent files are imported into .web files

@DanielAraldi DanielAraldi self-assigned this Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request web Web only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add support for React Native Web (BlurView)

3 participants