Morphin
Introduction
Button
Chip
Input
Searchbar
Sidebar

Chip

Chips are compact elements that can represent inputs, attributes, or actions. They can have an icon or avatar on the left, and a close button icon on the right. Chip is made using Tamagui Button

NormalIcon leftIcon right
IconsOutlinedDisabled
SelectedPressable
NormalIcon leftIcon right
IconsOutlinedDisabled
SelectedPressable

Code

import { XStack, YStack } from 'tamagui'
import { Chip } from 'yourpath/ui'
import { Eye, X } from '@tamagui/lucide-icons'

const DemoChip = () => {
  return (
    <>
      <YStack minHeight={250} overflow="hidden" gap="$4" margin="$3" padding="$2">
        <XStack gap={'$4'}>
          <Chip>Normal</Chip>
          <Chip icon={Eye}>Icon left</Chip>
          <Chip iconAfter={X}>Icon right</Chip>
        </XStack>
        <XStack gap={'$4'}>
          <Chip icon={Eye} iconAfter={X}>
            Icons
          </Chip>
          <Chip variant="outlined">Outlined</Chip>
          <Chip iconAfter={X} disabled>
            Disabled
          </Chip>
        </XStack>
        <XStack gap={'$4'}>
          <Chip iconAfter={X} theme={'active'}>
            Selected
          </Chip>
          <Chip iconAfter={X} pressTheme onPress={() => console.log('Do something')}>
            Pressable
          </Chip>
        </XStack>
      </YStack>
    </>
  )
}

export default DemoChip

Note: You need to add Chip theme from

here

. Inside Tamagui config add like below

Custom theme for Chip

import { customThemes } from './customTheme'

export const config = createTamagui({
  // Your config ....
  themes: {
    ...themes,
    ...customThemes, // This is how we add theme for Chip
  },
  tokens,
  // more things ....
})

Props

Chip
Prop NameTypeDescription
extendsChip (ChipProps) = Button (ButtonProps)It extends all the props from Button

Chip Code

import { getFontSize } from '@tamagui/font-size'
import { withStaticProperties } from '@tamagui/helpers'
import { useGetThemedIcon } from '@tamagui/helpers-tamagui'
import { ButtonNestingContext as ChipNestingContext, ThemeableStack } from '@tamagui/stacks'
import type { TextContextStyles, TextParentStyles } from '@tamagui/text'
import { SizableText, wrapChildrenInText } from '@tamagui/text'
import type {
  FontSizeTokens,
  GetProps,
  SizeTokens,
  ThemeableProps,
  VariantSpreadExtras,
} from '@tamagui/web'
import {
  createStyledContext,
  getVariableValue,
  spacedChildren,
  styled,
  useProps,
} from '@tamagui/web'
import type { FunctionComponent } from 'react'
import { useContext } from 'react'

import { getSpace } from '@tamagui/get-token'

const getChipSized = (val: SizeTokens | number, { tokens, props }: VariantSpreadExtras<any>) => {
  if (!val || props.circular) {
    return
  }
  if (typeof val === 'number') {
    return {
      paddingHorizontal: val * 0.25 * 2.5,
      borderRadius: props.circular ? 100_000 : val * 0.2 * 2,
    }
  }
  const xSize = getSpace(val, {
    shift: -1,
  })
  const radiusToken = tokens.radius[val] ?? tokens.radius['$true']
  return {
    paddingHorizontal: xSize,
    borderRadius: props.circular ? 100_000 : radiusToken,
  }
}

type ChipVariant = 'outlined'

export const ChipContext = createStyledContext<
  Partial<
    TextContextStyles & {
      size: SizeTokens
      variant?: ChipVariant
    }
  >
>({
  // keeping these here means they work with styled() passing down color to text
  color: undefined,
  ellipse: undefined,
  fontFamily: undefined,
  fontSize: undefined,
  fontStyle: undefined,
  fontWeight: undefined,
  letterSpacing: undefined,
  maxFontSizeMultiplier: undefined,
  size: undefined,
  textAlign: undefined,
  variant: undefined,
})

type ChipIconProps = { color?: any; size?: any }
type IconProp =
  | JSX.Element
  | FunctionComponent<ChipIconProps>
  | ((props: ChipIconProps) => any)
  | null

type ChipExtraProps = TextParentStyles &
  ThemeableProps & {
    /**
     * add icon before, passes color and size automatically if Component
     */
    icon?: IconProp
    /**
     * add icon after, passes color and size automatically if Component
     */
    iconAfter?: IconProp
    /**
     * adjust icon relative to size
     *
     * @default 1
     */
    scaleIcon?: number
    /**
     * make the spacing elements flex
     */
    spaceFlex?: number | boolean
    /**
     * adjust internal space relative to icon size
     */
    scaleSpace?: number
    /**
     * remove default styles
     */
    unstyled?: boolean

    selected?: boolean
  }

type ChipProps = ChipExtraProps & GetProps<typeof ChipFrame>

const CHIP_NAME = 'Chip'

const ChipFrame = styled(ThemeableStack, {
  name: CHIP_NAME,
  context: ChipContext,

  variants: {
    unstyled: {
      false: {
        size: '$true',
        justifyContent: 'center',
        alignItems: 'center',
        flexWrap: 'nowrap',
        flexDirection: 'row',
        hoverTheme: true,
        backgrounded: true,
        // Adds background color
        borderWidth: 0.5,
        borderColor: 'transparent',
        hoverStyle: {
          backgroundColor: '$color6',
          borderColor: '$color3',
        },
      },
    },

    variant: {
      outlined: {
        backgroundColor: 'transparent',
        borderWidth: 1.5,
        borderColor: '$borderColor',
        hoverStyle: {
          backgroundColor: 'transparent',
          borderColor: '$color6',
        },
      },
    },

    size: {
      '...size': getChipSized,
      ':number': getChipSized,
    },

    disabled: {
      true: {
        pointerEvents: 'none',
        opacity: 0.5,
      },
    },
  } as const,

  defaultVariants: {
    unstyled: process.env.TAMAGUI_HEADLESS === '1',
  },
})

const ChipText = styled(SizableText, {
  name: CHIP_NAME,
  context: ChipContext,

  variants: {
    unstyled: {
      false: {
        userSelect: 'none',
        // flexGrow 1 leads to inconsistent native style where text pushes to start of view
        flexGrow: 0,
        flexShrink: 1,
        ellipse: true,
        color: '$color',
      },
    },
  } as const,

  defaultVariants: {
    unstyled: process.env.TAMAGUI_HEADLESS === '1',
  },
})

const ChipIcon = (props: { children: React.ReactNode; scaleIcon?: number }) => {
  const { children, scaleIcon = 1 } = props
  const { size, color } = useContext(ChipContext)

  const iconSize =
    (typeof size === 'number' ? size * 0.5 : getFontSize(size as FontSizeTokens)) * scaleIcon

  const getThemedIcon = useGetThemedIcon({ size: iconSize, color: color as any })
  return getThemedIcon(children)
}

const ChipComponent = ChipFrame.styleable<ChipExtraProps>(function Chip(props, ref) {
  // @ts-ignore
  const { props: chipProps } = useChip(props)
  return <ChipFrame {...chipProps} ref={ref} />
})

/**
 * @deprecated Instead of useChip, see the Chip docs for the newer and much improved Advanced customization pattern: https://tamagui.dev/docs/components/button
 */
const chipStaticConfig = {
  inlineProps: new Set([
    // may be able to remove this entirely, as the compiler / runtime have gotten better
    'color',
    'fontWeight',
    'fontSize',
    'fontFamily',
    'fontStyle',
    'letterSpacing',
    'textAlign',
    'unstyled',
  ]),
}

const Chip = withStaticProperties(ChipComponent, {
  Text: ChipText,
  Icon: ChipIcon,
})

/**
 * @deprecated Instead of useChip, see the Chip docs for the newer and much improved Advanced customization pattern: https://tamagui.dev/docs/components/button
 */
function useChip<Props extends ChipProps>(
  { textProps, ...propsIn }: Props,
  { Text = Chip.Text }: { Text: any } = { Text: Chip.Text }
) {
  const isNested = useContext(ChipNestingContext)
  const propsActive = useProps(propsIn, {
    noNormalize: true,
    noExpand: true,
  }) as any as ChipProps

  // careful not to destructure and re-order props, order is important
  const {
    icon,
    iconAfter,
    space,
    spaceFlex,
    scaleIcon = 1,
    scaleSpace = 0.66,
    separator,
    noTextWrap,
    fontFamily,
    fontSize,
    fontWeight,
    fontStyle,
    letterSpacing,
    tag,
    ellipse,

    maxFontSizeMultiplier,

    ...restProps
  } = propsActive

  const size = propsActive.size || (propsActive.unstyled ? undefined : '$true')

  const color = propsActive.color as any

  const iconSize =
    (typeof size === 'number'
      ? size * 0.5
      : getFontSize(size as FontSizeTokens, {
          font: fontFamily?.[0] === '$' ? (fontFamily as any) : undefined,
        })) * scaleIcon

  const getThemedIcon = useGetThemedIcon({
    size: iconSize,
    color,
  })

  const [themedIcon, themedIconAfter] = [icon, iconAfter].map(getThemedIcon)
  const spaceSize = space ?? getVariableValue(iconSize) * scaleSpace
  const contents = noTextWrap
    ? [propsIn.children]
    : wrapChildrenInText(
        Text,
        {
          children: propsIn.children,
          fontFamily,
          fontSize,
          textProps,
          fontWeight,
          fontStyle,
          letterSpacing,
          ellipse,
          maxFontSizeMultiplier,
        },
        Text === ChipText && propsActive.unstyled !== true
          ? {
              unstyled: process.env.TAMAGUI_HEADLESS === '1',
              size,
            }
          : undefined
      )

  const inner = spacedChildren({
    space: spaceSize,
    spaceFlex,
    ensureKeys: true,
    separator,
    direction:
      propsActive.flexDirection === 'column' || propsActive.flexDirection === 'column-reverse'
        ? 'vertical'
        : 'horizontal',
    // for keys to stay the same we keep indices as similar a possible
    // so even if icons are undefined we still pass them
    children: [themedIcon, ...contents, themedIconAfter],
  })

  const props = {
    size,
    ...(propsIn.disabled && {
      // in rnw - false still has keyboard tabIndex, undefined = not actually focusable
      focusable: undefined,
      // even with tabIndex unset, it will keep focusVisibleStyle on web so disable it here
      focusVisibleStyle: {
        borderColor: '$background',
      },
    }),
    tag: tag ?? 'span',
    ...restProps,

    children: <ChipNestingContext.Provider value={true}>{inner}</ChipNestingContext.Provider>,
    // forces it to be a runtime pressStyle so it passes through context text colors
    disableClassName: true,
  } as Props

  return {
    spaceSize,
    isNested,
    props,
  }
}

export {
  Chip,
  ChipFrame,
  ChipIcon,
  chipStaticConfig,
  ChipText,
  // legacy
  useChip,
}
export type { ChipProps }