Skip to content

feat: Add Tag component for React Native#1053

Open
amandaye0h wants to merge 4 commits intomainfrom
feat-tag/react-native
Open

feat: Add Tag component for React Native#1053
amandaye0h wants to merge 4 commits intomainfrom
feat-tag/react-native

Conversation

@amandaye0h
Copy link
Copy Markdown
Contributor

@amandaye0h amandaye0h commented Apr 8, 2026

Description

What is the goal of this PR?

  • Creates a tag component that can be used in ListItems, Sections, and KeyValues

What are the key updates?

  • Adds a React Native Tag component with the variants, start/end icons props and conditional horizontal padding when icons are shown/not shown
  • Adds Code Connect (Tag.figma.tsx), stories, tests, and README/Figma link docs

Related issues

JIRA: https://consensyssoftware.atlassian.net/browse/DSYS-568

Manual testing steps

  1. Run yarn install > yarn build > yarn storybook:ios to see changes

Screenshots/Recordings

Before

Component did not exist before this.

After

Screen.Recording.2026-04-08.at.7.47.53.PM.mov

Pre-merge author checklist

  • I've followed MetaMask Contributor Docs
  • I've completed the PR template to the best of my ability
  • I’ve included tests if applicable
  • I’ve documented my code using JSDoc format if applicable
  • I’ve applied the right labels on the PR (see labeling guidelines). Not required for external contributors.

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Low Risk
Low risk: this is an additive UI component with storybook/docs/tests, plus a new export; it doesn’t modify existing business logic or data handling.

Overview
Introduces a new Tag component to design-system-react-native, including variant styling (background/text/icon color), optional start/end icons or accessories, and automatic padding adjustments based on icon presence.

Adds supporting package surface area and tooling: exports via components/index.ts, Storybook stories (and auto-generated storybook.requires.js entry), Code Connect mapping in Tag.figma.tsx, README documentation, and a new test suite covering rendering, styling, and icon/accessory behavior.

Reviewed by Cursor Bugbot for commit 55a1aac. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

📖 Storybook Preview

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

📖 Storybook Preview


figma.connect(
Tag,
'https://www.figma.com/design/1D6tnzXqWgnUC3spaAOELN/%F0%9F%A6%8A-MMDS-Components?node-id=12339-6553',
Copy link
Copy Markdown
Contributor Author

@amandaye0h amandaye0h Apr 8, 2026

Choose a reason for hiding this comment

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

Added this so there is better connection between code:design. It'll help with maintainability in the long term. I can remove it if this is premature at this stage of our DS.

If/when this PR is approved, I'll need to add the code link in Figma.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

📖 Storybook Preview

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

📖 Storybook Preview

@amandaye0h amandaye0h marked this pull request as ready for review April 8, 2026 12:21
@amandaye0h amandaye0h requested a review from a team as a code owner April 8, 2026 12:21
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 55a1aac. Configure here.

export { BannerAlert, BannerAlertSeverity } from './BannerAlert';
export type { BannerAlertProps } from './BannerAlert';

export { Tag, TagVariant, type TagProps } from './Tag';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Export pattern inconsistent with rest of file

Low Severity

The Tag export uses inline type syntax (export { Tag, TagVariant, type TagProps }) in a single statement, while every other component in this file (25+ components) uses separate export and export type statements. This is the only instance of the inline pattern in the entire file.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 55a1aac. Configure here.

Warning: 'warning',
Info: 'info',
} as const;
export type TagVariant = (typeof TagVariant)[keyof typeof TagVariant];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be in the shared package (you can get Cursor to help you align these props with cursor rules

flexDirection: BoxFlexDirection.Row,
alignItems: BoxAlignItems.Center,
twClassName: 'rounded-md self-start gap-0.5',
} as const;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

these styling should not be stored as consts. It should just be used inline

/**
* Visual emphasis (background and default text color for label / plain-text children).
*/
variant?: TagVariant;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A lot of these props can be extracted to shared (you can get cursor to do this)

/**
* Visual emphasis (background and default text color for label / plain-text children).
*/
variant?: TagVariant;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Additionally I think this prop should be named either severity or semantic cc @georgewrmarshall

/**
* Optional prop for a custom element to show at the start of the tag when no start icon is shown.
*/
startAccessory?: React.ReactNode;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

label, start Accessory, and endAccessory can be reused with BoxRow

const horizontalPaddingTw = [
finalStartIconName ? 'pl-1' : 'pl-1.5',
finalEndIconName ? 'pr-1' : 'pr-1.5',
].join(' ');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

don't store these consts. just use inline

Copy link
Copy Markdown
Contributor

@brianacnguyen brianacnguyen left a comment

Choose a reason for hiding this comment

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

I suggest to run each individual file through cursor to align with cursor rules

@brianacnguyen
Copy link
Copy Markdown
Contributor

@georgewrmarshall @amandaye0h should this component also includes a prop called includesSemanticIcon?

Copy link
Copy Markdown
Contributor

@georgewrmarshall georgewrmarshall left a comment

Choose a reason for hiding this comment

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

Hi Amanda, great first component PR 🎉 this is a well structured component and it's clear you've put real thought into the API design! The render helper pattern, variant color mapping, and accessory/icon separation are all clean. There are a few architectural requirements from our cursor rules and some alignment gaps with the Figma design to address before this is ready to merge. I've walked through each one with full context and included Cursor/Claude prompts where they can help. Nothing here is a fundamental problem — just refinements to get it fully aligned with our standards.

TextColor,
} from '../../types';

export const TagVariant = {
Copy link
Copy Markdown
Contributor

@georgewrmarshall georgewrmarshall Apr 10, 2026

Choose a reason for hiding this comment

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

Per ADR-0004, this const assertion should live in @metamask/design-system-shared/src/types/Tag/ rather than the React Native package. This matters because when a React (web) Tag component is built later, both platforms need to share the same const object — if it's defined in the platform package there's a risk they drift out of sync.

The mapping constants below (MAP_TAG_VARIANT_BACKGROUND, etc.) are fine to keep here since they reference React Native-specific tokens.

You can prompt Cursor/Claude to handle the migration:

Following .cursor/rules/component-architecture.md ADR-0004 pattern, move TagVariant
from Tag.constants.ts into packages/design-system-shared/src/types/Tag/Tag.types.ts,
create a TagPropsShared type there, update Tag.types.ts to import and extend it,
and update index.ts to export TagVariant directly from @metamask/design-system-shared.
Reference BadgeStatus as the golden path example.

};

/** Vertical padding (Tailwind `py-*`) */
export const TAG_PADDING_VERTICAL_TW = 'py-0';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

TAG_PADDING_VERTICAL_TW = 'py-0' has no effect — py-0 is the Tailwind default, so this is a no-op. Remove it.

TAG_LAYOUT below is used in exactly one place. Single-use constants like this add indirection without benefit — a future engineer has to jump between files to understand the layout. Move these values inline into Tag.tsx directly.

/**
* Visual emphasis (background and default text color for label / plain-text children).
*/
variant?: TagVariant;
Copy link
Copy Markdown
Contributor

@georgewrmarshall georgewrmarshall Apr 10, 2026

Choose a reason for hiding this comment

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

Curious on your thought here, I'm leaning towards two things here:

  1. Renaminh variant to severity. BannerAlert and IconAlert use severity for similar semantic set of states (neutral/info/success/error/warning). Keeping the prop name consistent across components makes the design system more predictable for consumers.

  2. The Figma component currently has primary where the code has Info — there is no info in Figma and no primary in the code. I'd recommend keeping Info (to stay consistent with BannerAlert) and updating the Figma component, but we could discuss before making this change

/**
* Label string; when set, shown instead of string/number `children`. Matches Figma `Label` in Code Connect.
*/
label?: string;
Copy link
Copy Markdown
Contributor

@georgewrmarshall georgewrmarshall Apr 10, 2026

Choose a reason for hiding this comment

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

suggestion: I realize label was in the original insight report but I think we can temove the label prop and textProps prop. BoxRow (which Tag will be used alongside) uses a TextOrChildren pattern — children is rendered as a Text component automatically when passed a string/number, and rendered as-is when passed a node. Tag should follow the same pattern. Having both label and children serve the same purpose creates a confusing API where two props compete.

In Tag.tsx and Tag.types.ts, remove the label and textProps props.
Update the component to render string/number children using the TextOrChildren
pattern from BoxRow — wrap string/number children in a Text component with
the severity's textColor, TextVariant.BodyXs, and FontWeight.Medium.
Node children should render as-is.

const meta: Meta<typeof Tag> = {
title: 'Components/Tag',
component: Tag,
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Per our component documentation standards, the first story must always be Default with minimal args and all controls wired up via argTypes. This powers the Storybook controls panel.

const meta: Meta<typeof Tag> = {
  title: 'Components/Tag',
  component: Tag,
  argTypes: {
    severity: {
      control: 'select',
      options: Object.values(TagSeverity),
    },
    startIconName: { control: 'select', options: Object.values(IconName) },
    endIconName: { control: 'select', options: Object.values(IconName) },
  },
};

export const Default: Story = {
  args: {
    children: 'Tag',
    severity: TagSeverity.Neutral,
  },
};

non-blocking: TagVariant is imported from './Tag.constants' — once moved to shared, import from '.' (the index) to match the pattern used across the codebase.

'https://www.figma.com/design/1D6tnzXqWgnUC3spaAOELN/%F0%9F%A6%8A-MMDS-Components?node-id=12339-6553',
{
props: {
variant: figma.enum('variant', {
Copy link
Copy Markdown
Contributor

@georgewrmarshall georgewrmarshall Apr 10, 2026

Choose a reason for hiding this comment

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

The Figma property is mistmatched type not variant. This mapping will silently produce no output in Dev Mode. Based on what the outcome is for variant/severity update to:

severity: figma.enum('severity', {
  neutral: TagSeverity.Neutral,
  info: TagSeverity.Info,
  success: TagSeverity.Success,
  error: TagSeverity.Error,
  warning: TagSeverity.Warning,
})

Hold off on publishing (yarn figma:connect:publish) until the severity/variant prop rename and info/primary Figma alignment are resolved. Once those are confirmed, run yarn figma:connect:publish:dry-run first to validate.

export { BannerAlert, BannerAlertSeverity } from './BannerAlert';
export type { BannerAlertProps } from './BannerAlert';

export { Tag, TagVariant, type TagProps } from './Tag';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

non-blocking: Every other component in this file uses separate export and export type statements. The inline type pattern here is the only instance of this style in the file:

// current
export { Tag, TagVariant, type TagProps } from './Tag';

// consistent with rest of file
export { Tag, TagSeverity } from './Tag';
export type { TagProps } from './Tag';

Note: once TagSeverity is moved to shared, this export will change anyway — tackle after the shared types migration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants