gluestack-ui icon indicating copy to clipboard operation
gluestack-ui copied to clipboard

Icons that require `fill` don't change color on native

Open aaronatbissell opened this issue 1 year ago • 23 comments

Description

When using a custom SVG Icon component that requires fill, changing that icon color using className='text-primary-500' doesn't work on native

CodeSandbox/Snack link

https://github.com/aaronatbissell/gluestack-issue-repro

Steps to reproduce

  1. Go to https://github.com/aaronatbissell/gluestack-issue-repro
  2. npm install
  3. npm run ios
  4. See the first icon doesn't change color

Code for the CheckIcon that needs to be filled:

const CheckIcon2 = createIcon({
  Root: Svg,
  viewBox: '0 0 448 512',
  path: (
    <>
    <Path
    d="M440.1 103C450.3 112.4 450.3 127.6 440.1 136.1L176.1 400.1C167.6 410.3 152.4 410.3 143 400.1L7.029 264.1C-2.343 255.6-2.343 240.4 7.029 231C16.4 221.7 31.6 221.7 40.97 231L160 350.1L407 103C416.4 93.66 431.6 93.66 440.1 103V103z"
    fill='currentColor'
    />
    </>
  ),
});
CheckIcon2.displayName = "CheckIcon2";
export {CheckIcon2};
<View style={{flex: 1}}>
  {/* Check Icon that needs to be "filled" */}
  <Icon as={CheckIcon2} size='xl' className='text-tertiary-500' />
  <Icon as={CheckIcon2} size='xl' className='text-primary-500' />
  <Icon as={CheckIcon2} size='xl' className='text-secondary-500' />
  <Icon as={CheckIcon2} size='xl' className='text-typography-950' />
</View>
{/* Gluestack Check Icon (only needs `stroke`) */}
<View style={{flex: 1}}>
  <Icon as={CheckIcon} size='xl' className='text-tertiary-500' />
  <Icon as={CheckIcon} size='xl' className='text-primary-500' />
  <Icon as={CheckIcon} size='xl' className='text-secondary-500' />
  <Icon as={CheckIcon} size='xl' className='text-typography-950' />
</View>

gluestack-ui Version

@gluestack-ui/icon: "0.1.22"

Platform

  • [X] Expo
  • [X] React Native CLI
  • [ ] Next
  • [ ] Web
  • [ ] Android
  • [ ] iOS

Other Platform

No response

Additional Information

No response

aaronatbissell avatar Aug 17 '24 20:08 aaronatbissell

Just another observation, if I set the fillColor on the Path of the SVG to something like red, then it displays correctly in red:

const CheckIcon2 = createIcon({
  Root: Svg,
  viewBox: '0 0 448 512',
  path: (
    <>
    <Path
    d="M440.1 103C450.3 112.4 450.3 127.6 440.1 136.1L176.1 400.1C167.6 410.3 152.4 410.3 143 400.1L7.029 264.1C-2.343 255.6-2.343 240.4 7.029 231C16.4 221.7 31.6 221.7 40.97 231L160 350.1L407 103C416.4 93.66 431.6 93.66 440.1 103V103z"
    fill='red' // <-- set this to `red` instead of `currentColor`
    />
    </>
  ),
});
CheckIcon2.displayName = "CheckIcon2";
export {CheckIcon2};

aaronatbissell avatar Aug 17 '24 20:08 aaronatbissell

Even the example on the bottom of the Icon docs doesn't appear to be working

const GluestackIcon = createIcon({
    // createIcon function is imported from '@gluestack-ui/themed'
    viewBox: "0 0 32 32",
    path: (
      <>
        {/* Rect, Path is imported from 'react-native-svg' */}
        <Rect width="32" height="32" rx="2" fill="currentColor" />
        <Path
          d="M9.5 14.6642L15.9999 9.87633V12.1358L9.5 16.9236V14.6642Z"
          fill="white"
        />
        <Path
          d="M22.5 14.6642L16.0001 9.87639V12.1359L22.5 16.9237V14.6642Z"
          fill="white"
        />
        <Path
          d="M9.5 19.8641L15.9999 15.0763V17.3358L9.5 22.1236V19.8641Z"
          fill="white"
        />
        <Path
          d="M22.5 19.8642L16.0001 15.0764V17.3358L22.5 22.1237V19.8642Z"
          fill="white"
        />
      </>
    ),
  })
<Icon as={GluestackIcon} size="xl" className="text-typography-black" />

aaronatbissell avatar Aug 18 '24 12:08 aaronatbissell

The issue has been resolved with the gluestack-ui icons. Please update your icon component. Thank you

rajat693 avatar Aug 20 '24 07:08 rajat693

This example from the icon docs still isn't working for me on v0.1.22: https://github.com/gluestack/gluestack-ui/issues/2385#issuecomment-2295241878

Screenshot 2024-09-25 at 12 20 38

export const GluestackIcon = createIcon({
  Root: Svg,
  viewBox: "0 0 32 32",
  path: (
    <>
      {/* Rect, Path is imported from 'react-native-svg' */}
      <Rect width="32" height="32" rx="2" fill="currentColor" />
      <Path d="M9.5 14.6642L15.9999 9.87633V12.1358L9.5 16.9236V14.6642Z" fill="white" />
      <Path d="M22.5 14.6642L16.0001 9.87639V12.1359L22.5 16.9237V14.6642Z" fill="white" />
      <Path d="M9.5 19.8641L15.9999 15.0763V17.3358L9.5 22.1236V19.8641Z" fill="white" />
      <Path d="M22.5 19.8642L16.0001 15.0764V17.3358L22.5 22.1237V19.8642Z" fill="white" />
    </>
  ),
});

// Usage:

      <Icon as={GluestackIcon} size="xl" className="text-typography-black" />

mnemitz avatar Sep 25 '24 11:09 mnemitz

I also had to make changes to my icon/index.tsx because I did a npx gluestack-ui add icon prior to #2387 getting merged in. If you did the same, then you will need to make the changes in #2387 manually or do the npx gluestack-ui add icon again.

aaronatbissell avatar Sep 25 '24 12:09 aaronatbissell

@aaronatbissell @mnemitz, Thanks for reporting we will have a look.

Viraj-10 avatar Sep 25 '24 12:09 Viraj-10

Thanks @Viraj-10 for the quick response! @aaronatbissell I re-added the icon package again as you mentioned to check, but still seeing a blue background when I set fill="currentColor".

Also thought I might flag that the GluestackIcon example on the docs page is missing the prop Root: Svg: https://gluestack.io/ui/docs/components/icon

mnemitz avatar Sep 25 '24 13:09 mnemitz

Thanks @mnemitz, We will add a test case for this. Since this is frequently breaking.

Viraj-10 avatar Sep 25 '24 13:09 Viraj-10

@aaronatbissell @mnemitz, Thanks for reporting we will have a look.

Just FYI - this is working for me now. I'm not sure if it's broken again, but it's working for me.

@mnemitz - I would double check that the changes in #2387 made it into your icon/index.tsx

aaronatbissell avatar Sep 25 '24 13:09 aaronatbissell

@aaronatbissell Just had a quick look and it looks like there are minor differences between the latest icon/index.tsx and the one from the change you linked. The main difference I can see is that the color prop isn't passed directly to the SVG/ 'as' component (not sure if that causes the issue).

I guess there is an explicit check for stroke:

    } else if (stroke === "currentColor" && color !== undefined) {
      colorProps = { ...colorProps, stroke: color };
    }

Probably want an analogous check for fill === "currentColor".

mnemitz avatar Sep 25 '24 14:09 mnemitz

How can I fill a gluestack-ui Icon in a gluestack-ui Button? I need the FavouriteIcon to be filled but could not find anything in the docs. Using fill property and tailwind classes did not work.

<Button variant="solid">
  <ButtonIcon as={FavouriteIcon} /> // should be filled
  <ButtonText>Saved to Favourites</ButtonText>
</Button>

ruckerzerg avatar Oct 28 '24 13:10 ruckerzerg

If you want to use fill when creating custom icons with createIcon() or using existing icons, try tailwind fake value You can put the value you want in the color variable and enter it

const color = "blue";

<Icon
  as={FavouriteIcon}
  size="md"
  className="fill-transparent text-transparent"
  color={color}
  fill={color}
/>

hyeonjun-commit avatar Feb 13 '25 23:02 hyeonjun-commit

@Viraj-10 @sra1kumar-NULL I can confirm this is still happening. I've created an example repo where this is happening with the latest expo app. I think @mnemitz was on to something with his suggestion

aaronatbissell avatar May 26 '25 23:05 aaronatbissell

is a fix still being worked on? its 2025

Ren-Logronio avatar Jul 10 '25 05:07 Ren-Logronio

I was wrestling with this for eight hours today, and eventually created a patched version of createIcon that works on native. You just submit the "fill-*" className and it works with that color.

<Icon as={MyIcon} size="md" className="fill-white" />

I don't have time to do a pull request, but the relevant code changes are here (modifying /node_modules/@gluestack-ui/icon/lib/createIcom/index.jsx):

Change line 27 from const { stroke = 'currentColor', color, role = 'img', ...resolvedProps } = finalProps; to: const { stroke = 'currentColor', fill, color, role = 'img', ...resolvedProps } = finalProps;

This gives us access to the fill color.

Change line 51 from {React.Children.map(children, (child, i) => (<ChildPath key={child?.key ?? i} element={child} {...child?.props}/>))} to {React.Children.map(children, (child, i) => (<ChildPath key={child?.key ?? i} element={child} {...child?.props} fill={fill || child?.props?.fill}/>))}

This pushes fill down to the child components of Svg.

My understanding is that on web, react-native-svg allows the fill color to be applied to the Svg component, which automatically pushes this color down to children. On native, that doesn't happen, so we have to push the fill color to children manually.

The way I did this on a single native project was to hijack the "import { createIcon } ..." statement in the /components/Gluestack/icon/index.tsx file to point to my modified version of createIcon.

OneHatRepo avatar Aug 01 '25 02:08 OneHatRepo

I was wrestling with this for eight hours today, and eventually created a patched version of createIcon that works on native. You just submit the "fill-*" className and it works with that color.

<Icon as={MyIcon} size="md" className="fill-white" />

I don't have time to do a pull request, but the relevant code changes are here (modifying /node_modules/@gluestack-ui/icon/lib/createIcom/index.jsx):

Change line 27 from const { stroke = 'currentColor', color, role = 'img', ...resolvedProps } = finalProps; to: const { stroke = 'currentColor', fill, color, role = 'img', ...resolvedProps } = finalProps;

This gives us access to the fill color.

Change line 51 from {React.Children.map(children, (child, i) => (<ChildPath key={child?.key ?? i} element={child} {...child?.props}/>))} to {React.Children.map(children, (child, i) => (<ChildPath key={child?.key ?? i} element={child} {...child?.props} fill={fill || child?.props?.fill}/>))}

This pushes fill down to the child components of Svg.

My understanding is that on web, react-native-svg allows the fill color to be applied to the Svg component, which automatically pushes this color down to children. On native, that doesn't happen, so we have to push the fill color to children manually.

The way I did this on a single native project was to hijack the "import { createIcon } ..." statement in the /components/Gluestack/icon/index.tsx file to point to my modified version of createIcon.

This doesn't seem to work for me. Instead it even breaks other working functionalities.

onyedikachi23 avatar Aug 12 '25 03:08 onyedikachi23

The problem is the default fill-none applied to iconStyle. Here:


const iconStyle = tva({
	// The "fill-none" here
	base: "text-typography-950 fill-none pointer-events-none",
	variants: {
	// variant props
		},
	},
});

This is because NativeWind takes the fill-none and uses it to overrides your fill prop, caused by this:


cssInterop(
	// In some places (like  in <BadgeIcon />), this is PrimitiveIcon
	UIIcon,
	{
		className: {
			target: "style",
			nativeStyleToProp: {
				height: true,
				width: true,
				fill: true,
				color: "classNameColor",
				stroke: true,
			},
		},
	},
);

This fill-none causes a style of {fill: "transparent"} which overrides the fill prop you set, like <Icon fill="red" />.

To prove this, try out the below yourself:

// This icon will render with a red fill color.
<Icon className="fill-red-500" />;

// This icon will not render with a red fill color.
// The prop is being overridden by the default `fill-none`.
<Icon fill="red" />;

// This icon will render with a blue-500 fill color.
// The fill color from the className prop takes precedence over the fill prop.
<Icon className="fill-blue-500" fill="green" />;

The fix is to remove the default fill-none from the icon style. You have to do this for any GlueStack Icon component you're using like Icon, ButtonIcon, BadgeIcon, and so on, then optionally replace the intended usage with fill="transparent" like this:


const iconStyle = tva({
	// "fill-none" has been removed here
	base: "text-typography-950 pointer-events-none",
	variants: {
	// variant props
		},
	},
});


 const Icon: React.FC<IconProps> = ({
    size = "md",
	// Opitionally set the default `fill-none` here
    fill = "transparent",
    className,
    ...props
}) => {
    // ... rest of the component logic
return (
			<UIIcon
				{...props}
				fill={fill}
				className={iconStyle({ class: className })}
			/>
		);
};

@Viraj-10 @sra1kumar-NULL you might want to look into this.

onyedikachi23 avatar Aug 12 '25 06:08 onyedikachi23

In addition to @onyedikachi23 mod, svg like this got extra stroke and incorrect fill

const SVGComponent = (props: SvgProps) => (
  <Svg
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 24 24"
    fill="currentColor"
    {...props}
  >
    <Path d="m12 2.586 6.207 6.207-1.414 1.414L13 6.414V16h-2V6.414l-3.793 3.793-1.414-1.414zM3 18v-4h2v4a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-4h2v4a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3" />
  </Svg>
);
{/* svg icon */}
<Share width={64} height={64} color="red" />
<Icon as={Share} className="h-[64] w-[64] text-[#f00]" />

{/* icon in ui/icon */}
<ArrowUpIcon width={64} height={64} color="red" />
<Icon as={ArrowUpIcon} className="h-[64] w-[64] text-[#f00]" />
Image

Patch

Apply patch remove fill-none of ui/icon/index.tsx, add fill: 'none' and stroke="currentColor" to the ArrowUpIcon

Patch
diff --git a/node_modules/@gluestack-ui/icon/src/.DS_Store b/node_modules/@gluestack-ui/icon/src/.DS_Store
new file mode 100644
index 0000000..ecbf185
Binary files /dev/null and b/node_modules/@gluestack-ui/icon/src/.DS_Store differ
diff --git a/node_modules/@gluestack-ui/icon/src/createIcon/index.tsx b/node_modules/@gluestack-ui/icon/src/createIcon/index.tsx
index 0cc88b3..0a2f376 100644
--- a/node_modules/@gluestack-ui/icon/src/createIcon/index.tsx
+++ b/node_modules/@gluestack-ui/icon/src/createIcon/index.tsx
@@ -25,6 +25,7 @@ interface CreateIconOptions {
    * Default props automatically passed to the component; overwritable
    */
   defaultProps?: any;
+  fill?:string;
   type?: any;
 }
 
@@ -67,12 +68,7 @@ export function createIcon<IconProps>({
       ...props,
     };
 
-    const {
-      stroke = 'currentColor',
-      color,
-      role = 'img',
-      ...resolvedProps
-    } = finalProps;
+    const { stroke, color, fill, role = 'img', ...resolvedProps } = finalProps;
     let type = resolvedProps.type;
     if (type === undefined) {
       type = 'svg';
@@ -84,6 +80,9 @@ export function createIcon<IconProps>({
     if (stroke) {
       colorProps = { ...colorProps, stroke: stroke };
     }
+    if (fill) {
+      colorProps = {...colorProps, fill: fill};
+    }
 
     let sizeProps = {};
     let sizeStyle = {};
diff --git a/node_modules/@gluestack-ui/icon/src/primitiveIcon/index.tsx b/node_modules/@gluestack-ui/icon/src/primitiveIcon/index.tsx
index 5940b49..a59828b 100644
--- a/node_modules/@gluestack-ui/icon/src/primitiveIcon/index.tsx
+++ b/node_modules/@gluestack-ui/icon/src/primitiveIcon/index.tsx
@@ -28,7 +28,7 @@ export const PrimitiveIcon = React.forwardRef<
       color,
       classNameColor,
       size,
-      stroke = 'currentColor',
+      stroke,
       as: AsComp,
       style,
       ...props
@@ -48,10 +48,11 @@ export const PrimitiveIcon = React.forwardRef<
     if (fill) {
       colorProps = { ...colorProps, fill: fill };
     }
-    if (stroke !== 'currentColor') {
+    if (stroke) {
       colorProps = { ...colorProps, stroke: stroke };
-    } else if (stroke === 'currentColor' && color !== undefined) {
-      colorProps = { ...colorProps, stroke: color };
+    }
+    if (color) {
+      colorProps = { ...colorProps, color: color };
     }
 
     if (AsComp) {
Image

@mnemitz 's gluestack logo should works too Image


@Viraj-10 @sra1kumar-NULL I think the problem is createIcon and primitiveIcon applied the stroke regardless of whether the icon actually needs and ui/icon/index.tsx adds an unnecessary fill-none, which makes the Share icon hollow. It would be better to explicitly specify whether a fill or stroke is needed when creating icons, rather than assuming all icons are stroke-only with no fill by default.

likaci avatar Aug 13 '25 02:08 likaci

@onyedikachi23 I don't know why my solution wasn't working for you. I created a new project today and my createIcon fix works with that one too. However, my solution is simply a temporary workaround, so if you folks can figure out a more permanent solution, that'd be great!

OneHatRepo avatar Aug 16 '25 20:08 OneHatRepo

@OneHatRepo I'm thankful for your suggested solution, but I tried it and didn't work at all.

Anyways, I've figured out the exact problem, and which I've explained and given the solution here. It's a very simple fix that doesn't need modifying files in node_modules, like you suggested.

onyedikachi23 avatar Aug 16 '25 20:08 onyedikachi23

is this issue already fixed? its October 2025

Papazy avatar Oct 06 '25 10:10 Papazy

still happening on v3 too.

tgmarinho avatar Oct 27 '25 20:10 tgmarinho

@onyedikachi23 your solution is not working on v3

tgmarinho avatar Oct 27 '25 20:10 tgmarinho