diff --git a/src/components/error/error-boundary.tsx b/src/components/error/error-boundary.tsx
new file mode 100644
index 0000000..7d13d8b
--- /dev/null
+++ b/src/components/error/error-boundary.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+
+import IonIcon from '../icons/icons';
+import { ErrorBoundaryStyled } from './styled';
+
+export interface ErrorBoundaryProps {
+ msg: string;
+}
+const sizeIcon = 16;
+const iconType = 'info';
+
+const ErrorBoundary = ({ msg }: ErrorBoundaryProps) => {
+ return (
+
+
+
+
+ {msg}
+
+
+ );
+};
+
+export default ErrorBoundary;
diff --git a/src/components/error/styled.ts b/src/components/error/styled.ts
new file mode 100644
index 0000000..98096fe
--- /dev/null
+++ b/src/components/error/styled.ts
@@ -0,0 +1,45 @@
+import stitches from '../../stitches.config';
+
+const { styled } = stitches;
+
+export const ErrorBoundaryStyled = styled('div', {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: '8px 16px 8px 12px',
+ gap: 8,
+
+ width: 'max-content',
+ height: 24,
+
+ backgroundColor: '$warning1',
+
+ borderWidth: '1px 1px 1px 8px',
+ borderStyle: 'solid',
+ borderColor: '$warning7',
+ borderRadius: '8px',
+
+ fontSize: 14,
+ color: '$neutral8',
+
+ svg: {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+
+ backgroundColor: '$warning7',
+ fill: '$warning1',
+
+ borderRadius: 8,
+ },
+
+ div: {
+ display: 'flex',
+ gap: 4,
+ },
+
+ label: {
+ fontWeight: 'bold',
+ },
+});
diff --git a/src/components/index.ts b/src/components/index.ts
index b99ee7c..01c2035 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -1,3 +1,4 @@
export { default as Button } from './button';
export { default as Icon } from './icons';
export { default as IonChip } from './chip';
+export { default as IonTag } from './tag';
diff --git a/src/components/tag/index.ts b/src/components/tag/index.ts
new file mode 100644
index 0000000..4a514b3
--- /dev/null
+++ b/src/components/tag/index.ts
@@ -0,0 +1 @@
+export { default } from './tag';
diff --git a/src/components/tag/styles.ts b/src/components/tag/styles.ts
new file mode 100644
index 0000000..b4b3f15
--- /dev/null
+++ b/src/components/tag/styles.ts
@@ -0,0 +1,59 @@
+import stitches from '../../stitches.config';
+
+const { styled } = stitches;
+
+const setColors = (bgColor: string, color: string) => ({
+ backgroundColor: bgColor,
+ color: color,
+ svg: {
+ fill: color,
+ },
+});
+
+export const TagStyle = styled('div', {
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: '2px 8px',
+ gap: '6px',
+
+ width: 'max-content',
+ height: 'max-content',
+ minHeight: '20px',
+
+ backgroundColor: '$whitetransparence90',
+ color: '$neutral7',
+ borderRadius: '50px',
+
+ span: {
+ fontSize: '12px',
+ fontWeight: '400',
+ lineHeight: '16px',
+ },
+
+ variants: {
+ status: {
+ success: {
+ ...setColors('$positive1', '$positive7'),
+ },
+ info: {
+ ...setColors('$info1', '$info7'),
+ },
+ warning: {
+ ...setColors('$warning1', '$warning7'),
+ },
+ negative: {
+ ...setColors('$negative1', '$negative7'),
+ },
+ neutral: {
+ ...setColors('$neutral2', '$neutral7'),
+ },
+ },
+ outline: {
+ true: {
+ border: '1px solid',
+ },
+ },
+ },
+});
diff --git a/src/components/tag/tag.test.tsx b/src/components/tag/tag.test.tsx
new file mode 100644
index 0000000..3bfa52d
--- /dev/null
+++ b/src/components/tag/tag.test.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+
+import IonTag, { IonTagProps } from './tag';
+import { StatusType } from '../../core/types/status';
+
+const defaultTag: IonTagProps = {
+ label: 'tag label',
+};
+
+const sut = (props = defaultTag) => render();
+const tagId = 'ion-tag';
+const getTag = () => screen.getByTestId(tagId);
+
+const customColor = '#AADD00';
+
+describe('IonTag', () => {
+ it('should render default tag ', () => {
+ sut();
+ expect(getTag()).toBeTruthy();
+ });
+
+ it('should render tag with label "example tag"', async () => {
+ const customLabel = 'example tag';
+ await sut({ label: customLabel });
+ expect(screen.findByText(customLabel)).toBeTruthy();
+ });
+
+ it.each([
+ 'success',
+ 'info',
+ 'warning',
+ 'negative',
+ 'neutral',
+ ] as StatusType[])('should render tag with status: %s', (status) => {
+ sut({ ...defaultTag, status: status });
+ expect(getTag().className).toContain(`status-${status}`);
+ });
+
+ it('should not render outline in tag', async () => {
+ await sut({ ...defaultTag, outline: false });
+ expect(getTag().className).not.toContain('outline-false');
+ });
+
+ it('should render outline in tag', async () => {
+ await sut({ ...defaultTag, outline: true });
+ expect(getTag().className).toContain('outline-true');
+ });
+
+ it('should render tag with icon check', async () => {
+ const iconType = 'check';
+ await sut({ ...defaultTag, icon: iconType });
+ const icon = screen.getByTestId(`ion-icon-${iconType}`);
+ expect(icon).toBeTruthy();
+ });
+
+ it('should render ErrorBoundary component when not exist label', async () => {
+ sut({ label: '' });
+ const msgError = 'Label cannot be empty';
+ const errorBoundary = screen.getByTestId('ion-error-boundary');
+ expect(errorBoundary).toBeTruthy();
+ expect(await screen.findByText(msgError)).toBeTruthy();
+ });
+
+ it('should render tag with custom color', async () => {
+ await sut({ ...defaultTag, color: customColor });
+ expect(getTag().className).not.toContain('status');
+ });
+
+ it('should render the tag the same as it has a custom color', async () => {
+ const statusInfo = 'status-info';
+ await sut({ ...defaultTag, status: 'info', color: customColor });
+ expect(getTag().className).toContain(statusInfo);
+ });
+});
diff --git a/src/components/tag/tag.tsx b/src/components/tag/tag.tsx
new file mode 100644
index 0000000..51f3daf
--- /dev/null
+++ b/src/components/tag/tag.tsx
@@ -0,0 +1,69 @@
+import React from 'react';
+
+import { TagStyle } from './styles';
+import IonIcon from '../icons/icons';
+
+import { validateHexColor } from '../utils/validateHexColor';
+import ErrorBoundary from '../error/error-boundary';
+import { TagStatus } from '../../core/types/status';
+import { iconType } from '../icons/svgs/icons';
+
+export interface IonTagProps {
+ outline?: boolean;
+ status?: TagStatus;
+ color?: string;
+ label: string;
+ icon?: iconType;
+}
+
+const iconSize = 12;
+const defaultColor = '#505566';
+const lighteningFactor = '1A';
+
+const isValidLabel = (label: string) => label && !(String(label).trim() === '');
+
+const newColor = (color: string) => ({
+ backgroundColor: color + lighteningFactor,
+ color: color,
+ fill: color,
+});
+
+const getColorObject = (status?: TagStatus, color?: string) => {
+ if (status) {
+ return {};
+ }
+
+ if (!color || !validateHexColor(color)) {
+ return { ...newColor(defaultColor) };
+ }
+
+ return {
+ ...newColor(color),
+ };
+};
+
+const IonTag = ({
+ label,
+ color,
+ icon,
+ status,
+ outline = true,
+}: IonTagProps) => {
+ if (!isValidLabel(label)) {
+ return ;
+ }
+
+ return (
+
+ {icon && }
+ {label}
+
+ );
+};
+
+export default IonTag;
diff --git a/src/components/utils/validateHexColor.ts b/src/components/utils/validateHexColor.ts
new file mode 100644
index 0000000..59020d3
--- /dev/null
+++ b/src/components/utils/validateHexColor.ts
@@ -0,0 +1,3 @@
+export const validateHexColor = (color: string): boolean => {
+ return /^#(?:[0-9a-fA-F]{3,4}){1,2}$/.test(color);
+};
diff --git a/src/core/types/status.ts b/src/core/types/status.ts
new file mode 100644
index 0000000..2ab0084
--- /dev/null
+++ b/src/core/types/status.ts
@@ -0,0 +1,2 @@
+export type StatusType = 'success' | 'info' | 'warning' | 'negative';
+export type TagStatus = 'success' | 'info' | 'warning' | 'negative' | 'neutral';
diff --git a/src/stories/tag/tag.stories.tsx b/src/stories/tag/tag.stories.tsx
new file mode 100644
index 0000000..96b72f7
--- /dev/null
+++ b/src/stories/tag/tag.stories.tsx
@@ -0,0 +1,84 @@
+import { ComponentStory, ComponentMeta } from '@storybook/react';
+
+import IonTag, { IonTagProps } from '../../components/tag/tag';
+
+export default {
+ title: 'Ion/Data Display/Tag',
+ component: IonTag,
+} as ComponentMeta;
+
+const Template: ComponentStory = (args: IonTagProps) => (
+
+);
+
+export const tagDefault = Template.bind({});
+tagDefault.storyName = 'type: default';
+tagDefault.args = {
+ label: 'Exemple Message',
+};
+
+export const TagWithoutOutline = Template.bind({});
+TagWithoutOutline.storyName = 'type: without outline';
+TagWithoutOutline.args = {
+ label: 'Exemple Message',
+ outline: false,
+};
+
+export const TagWithStatusSuccess = Template.bind({});
+TagWithStatusSuccess.storyName = 'type: success';
+TagWithStatusSuccess.args = {
+ label: 'tag with status success',
+ status: 'success',
+};
+
+export const TagWithStatusWarning = Template.bind({});
+TagWithStatusWarning.storyName = 'type: warning';
+TagWithStatusWarning.args = {
+ label: 'Exemple Message',
+ status: 'warning',
+};
+
+export const TagWithStatusInfo = Template.bind({});
+TagWithStatusInfo.storyName = 'type: info';
+TagWithStatusInfo.args = {
+ label: 'Exemple Message',
+ status: 'info',
+};
+
+export const TagWithStatusNegative = Template.bind({});
+TagWithStatusNegative.storyName = 'type: negative';
+TagWithStatusNegative.args = {
+ label: 'Exemple Message',
+ status: 'negative',
+};
+
+export const TagWithStatusNeutral = Template.bind({});
+TagWithStatusNeutral.storyName = 'type: neutral';
+TagWithStatusNeutral.args = {
+ label: 'Exemple Message',
+ status: 'neutral',
+};
+
+export const TagCustomColor = Template.bind({});
+TagCustomColor.storyName = 'type: custom';
+TagCustomColor.args = {
+ label: 'Exemple Message',
+ color: '#7f0dff',
+};
+
+export const tagWithIcon = Template.bind({});
+tagWithIcon.storyName = 'type: with icon';
+tagWithIcon.args = {
+ label: 'Exemple Message',
+ icon: 'check',
+ status: 'success',
+ outline: true,
+};
+
+export const tagError = Template.bind({});
+tagError.storyName = 'type: Error';
+tagError.args = {
+ label: '',
+ icon: 'check',
+ status: 'success',
+};