Repository: code-obos/grunnmuren
Pull request #1228: TagGroup
Author: login: gabrielwallin
Created: 2025-03-10T15:03:20Z | Merged: 2025-03-19T14:29:36Z

## Tags! (Chips)
This pull request adds the `tag-group` component in the `packages/react` directory. 
Its mostly based on the implementation in [RAC](https://react-spectrum.adobe.com/react-aria/TagGroup.html), but with some differences, for example the "removal" button which is wrapping the whole tag, instead of being a smaller section. We expose all three components in a composable style, allowing the users to construct the html-tree themselves with `TagGroup`, `TagList` and `Tag`

One thing that is still not optimal is the VO-compatibility issues: https://github.com/adobe/react-spectrum/issues/7934

Copilot summarizes the pull as:
* [`packages/react/src/tag-group/tag-group.tsx`](diffhunk://#diff-1a84c4c97c694f2192cdea3dd984afb2d91c73d8cea4caa43c5a24e0100c21faR1-R130): Introduced `TagGroup`, `TagList`, and `Tag` components along with their respective types (`TagGroupProps`, `TagListProps`, `TagProps`). These components include functionalities for selection modes and removable tags.
* [`packages/react/src/tag-group/index.ts`](diffhunk://#diff-41e0e35ede54c6ded9bad21aa08ba40ef2b2fc58e4835fea8a4178d116b0aa35R1): Added export for all components from `tag-group`.
* [`packages/react/src/tag-group/tag-group.stories.tsx`](diffhunk://#diff-d7dd3964b247143f290bd0a39fbe388addeb6fbb0fe477692b19bdb1c10921b7R1-R167): Added multiple storybook examples demonstrating different selection modes, icons, and removable tags for the `UNSAFE_TagGroup` component.

<img width="753" alt="image" src="https://github.com/user-attachments/assets/8b1ef1dc-5a6f-4f6c-86b3-7b868cc00602" />



Diff:
diff --git a/.changeset/fine-hotels-tie.md b/.changeset/fine-hotels-tie.md
new file mode 100644
index 000000000..2c104a69d
--- /dev/null
+++ b/.changeset/fine-hotels-tie.md
@@ -0,0 +1,5 @@
+---
+"@obosbbl/grunnmuren-react": minor
+---
+
+Add TagGroup component (aka Chips)
diff --git a/packages/react/src/tag-group/index.ts b/packages/react/src/tag-group/index.ts
new file mode 100644
index 000000000..935776c69
--- /dev/null
+++ b/packages/react/src/tag-group/index.ts
@@ -0,0 +1 @@
+export * from './tag-group';
diff --git a/packages/react/src/tag-group/tag-group.stories.tsx b/packages/react/src/tag-group/tag-group.stories.tsx
new file mode 100644
index 000000000..49672a786
--- /dev/null
+++ b/packages/react/src/tag-group/tag-group.stories.tsx
@@ -0,0 +1,176 @@
+import { Calendar, House } from '@obosbbl/grunnmuren-icons-react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { useState } from 'react';
+import type { Selection } from 'react-aria-components';
+
+import { Description, Label } from '../label';
+import {
+  UNSAFE_Tag as Tag,
+  UNSAFE_TagGroup as TagGroup,
+  UNSAFE_TagList as TagList,
+} from './tag-group';
+
+const meta: Meta<typeof TagGroup> = {
+  title: 'TagGroup',
+  component: TagGroup,
+  parameters: {
+    layout: 'fullscreen',
+  },
+};
+
+export default meta;
+
+type Story = StoryObj<typeof TagGroup>;
+
+export const Default: Story = {
+  args: {
+    selectionMode: 'single',
+  },
+  render: (props) => (
+    <div className="p-6">
+      <TagGroup {...props}>
+        <Label>Velg en:</Label>
+        <TagList className="my-2">
+          <Tag id="tag1">Tag 1</Tag>
+          <Tag id="tag2">Tag 2</Tag>
+          <Tag id="tag3">Tag 3</Tag>
+        </TagList>
+      </TagGroup>
+    </div>
+  ),
+};
+
+export const SelectionModes = () => {
+  return (
+    <div className="space-y-6 p-6">
+      <div>
+        <TagGroup defaultSelectedKeys={['tag1']}>
+          <Label>Single Selection (default)</Label>
+          <TagList className="my-2">
+            <Tag id="tag1">Tag 1</Tag>
+            <Tag id="tag2">Tag 2</Tag>
+            <Tag id="tag3">Tag 3</Tag>
+          </TagList>
+        </TagGroup>
+      </div>
+
+      <div>
+        <TagGroup
+          selectionMode="multiple"
+          defaultSelectedKeys={['tag1', 'tag3']}
+        >
+          <Label>Multiple Selection</Label>
+
+          <TagList className="my-2">
+            <Tag id="tag1">Tag 1</Tag>
+            <Tag id="tag2">Tag 2</Tag>
+            <Tag id="tag3">Tag 3</Tag>
+          </TagList>
+        </TagGroup>
+      </div>
+    </div>
+  );
+};
+
+export const WithIcons = () => {
+  return (
+    <div className="p-6">
+      <TagGroup defaultSelectedKeys={['tag1']}>
+        <Label>Velg et sted:</Label>
+        <TagList className="my-2">
+          <Tag id="tag1" textValue="Bislett">
+            <House /> Bislett
+          </Tag>
+          <Tag id="tag2" textValue="Fredensborg">
+            <House /> Fredensborg
+          </Tag>
+          <Tag id="tag3" textValue="Majorstuen">
+            <House /> Majorstuen
+          </Tag>
+        </TagList>
+      </TagGroup>
+    </div>
+  );
+};
+
+export const CalendarTags = () => {
+  return (
+    <div className="p-6">
+      <TagGroup defaultSelectedKeys={['slot1']}>
+        <Label>Velg en tid:</Label>
+        <TagList className="my-2 flex flex-wrap gap-2">
+          <Tag id="slot1" textValue="11:00 - 12:00">
+            <Calendar /> 11:00 - 12:00
+          </Tag>
+          <Tag id="slot2" textValue="13:30 - 14:30">
+            <Calendar /> 13:30 - 14:30
+          </Tag>
+          <Tag id="slot3" textValue="16:00 - 17:00">
+            <Calendar /> 16:00 - 17:00
+          </Tag>
+        </TagList>
+        <Description>
+          Velg en tid som passer for deg. Du kan kun velge én tid.
+        </Description>
+      </TagGroup>
+    </div>
+  );
+};
+
+export const RemovableTags = () => {
+  const [tags, setTags] = useState(['Oslo', 'Stavanger', 'Göteborg']);
+
+  const handleRemove = (keys: React.Key | Set<React.Key>) => {
+    // Convert single key to Set for consistent handling
+    const keysSet = keys instanceof Set ? keys : new Set([keys]);
+    setTags(tags.filter((_, index) => !keysSet.has(`tag-${index}`)));
+  };
+
+  return (
+    <div className="p-6">
+      <TagGroup onRemove={handleRemove}>
+        <Label>Aktive filter:</Label>
+        <TagList className="my-2">
+          {tags.map((tag, index) => (
+            <Tag
+              id={`tag-${index}`}
+              /* biome-ignore lint/suspicious/noArrayIndexKey: This is a storybook */
+              key={index}
+            >
+              {tag}
+            </Tag>
+          ))}
+        </TagList>
+      </TagGroup>
+      {tags.length === 0 && (
+        <p className="mt-4">All tags removed! Refresh to try again.</p>
+      )}
+    </div>
+  );
+};
+
+export const ControlledSelection = () => {
+  const [selectedKeys, setSelectedKeys] = useState<Selection>(
+    new Set(['tag1']),
+  );
+
+  return (
+    <div className="p-6">
+      <TagGroup
+        selectedKeys={selectedKeys}
+        onSelectionChange={setSelectedKeys}
+        selectionMode="multiple"
+      >
+        <Label>Velg flere:</Label>
+        <TagList className="my-2">
+          <Tag id="tag1">Tag 1</Tag>
+          <Tag id="tag2">Tag 2</Tag>
+          <Tag id="tag3">Tag 3</Tag>
+        </TagList>
+      </TagGroup>
+      <div className="mt-4">
+        Selected: {Array.from(selectedKeys).join(', ')}
+      </div>
+    </div>
+  );
+};
diff --git a/packages/react/src/tag-group/tag-group.tsx b/packages/react/src/tag-group/tag-group.tsx
new file mode 100644
index 000000000..b8016d91d
--- /dev/null
+++ b/packages/react/src/tag-group/tag-group.tsx
@@ -0,0 +1,143 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva, cx } from 'cva';
+import type { RefAttributes } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  TagList as RACTagList,
+  type TagListProps as RACTagListProps,
+  type TagProps as RACTagProps,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'relative flex cursor-pointer items-center gap-1 rounded-lg px-2 py-1 font-medium text-sm transition-colors duration-200',
+    //Focus
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    //Border
+    'border border-2 border-blue-dark',
+    //Backgrounds
+    'data-[hovered]:!bg-sky bg-white text-black aria-selected:bg-sky-light data-[allows-removing]:bg-sky-light',
+    //Icons
+    '[&_svg]:h-4 [&_svg]:w-4',
+  ],
+});
+
+export type TagGroupProps = Omit<RACTagGroupProps, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    /**
+     * CSS classes to apply to the tag group
+     */
+    className?: string;
+    /**
+     * The function to call when the tag is removed
+     */
+    onRemove?: (key: React.Key) => void;
+
+    /**
+     * The selection mode for the tag group
+     * @default "single"
+     */
+    selectionMode?: 'single' | 'multiple';
+  };
+
+//The usage of <object> here could probably be replaced with a generic for more type safety in usage
+export type TagListProps = Omit<RACTagListProps<object>, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    /**
+     * CSS classes to apply to the tag list
+     */
+    className?: string;
+  };
+
+export type TagProps = Omit<RACTagProps, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    children: React.ReactNode;
+    /**
+     * CSS classes to apply to the tag
+     */
+    className?: string;
+  };
+
+/**
+ * A group component for Tag components that enables selection and organization of options.
+ */
+function TagGroup(props: TagGroupProps) {
+  const {
+    onRemove,
+    selectionMode = 'single',
+    className,
+    children,
+    ...restProps
+  } = props;
+
+  return (
+    <RACTagGroup
+      {...restProps}
+      className={className}
+      selectionMode={onRemove ? 'none' : selectionMode}
+      onRemove={onRemove}
+    >
+      {children}
+    </RACTagGroup>
+  );
+}
+
+/**
+ * A container component for Tag components within a TagGroup.
+ */
+function TagList(props: TagListProps) {
+  const { className, children, ...restProps } = props;
+
+  return (
+    <RACTagList
+      {...restProps}
+      className={cx('flex flex-wrap gap-2', className)}
+    >
+      {children}
+    </RACTagList>
+  );
+}
+
+/**
+ * Interactive tag component for selections, filtering, and categorization.
+ */
+function Tag(props: TagProps) {
+  const { className, children, ...restProps } = props;
+
+  const textValue = typeof children === 'string' ? children : undefined;
+
+  return (
+    <RACTag
+      className={tagVariants({
+        className,
+      })}
+      textValue={textValue}
+      {...restProps}
+    >
+      {({ allowsRemoving }) =>
+        allowsRemoving ? (
+          <>
+            {children}
+            <Button
+              className="outline-none after:absolute after:top-0 after:right-0 after:bottom-0 after:left-0"
+              slot="remove"
+            >
+              <Close className="ml-1" />
+            </Button>
+          </>
+        ) : (
+          children
+        )
+      }
+    </RACTag>
+  );
+}
+
+export {
+  TagGroup as UNSAFE_TagGroup,
+  TagList as UNSAFE_TagList,
+  Tag as UNSAFE_Tag,
+};


Reviews:
user: login: alexanbj
body: Synes dette er en lovende start!

Noen umiddelbare tanker:
* Skal vi støtte disabled? Det er noe vi ikke har på feks Button i dag
* På andre klikkbare ting har vi `cursor: pointer`, her er det kun på X-en, som føles litt rart ut.
* Se på om den skal hete Chips eller TagGroup
* Jeg tror kanskje vi må eksportere TagList, ikke automatisk rendre den. Dette er eeeegentlig en slags form komponent, som det må knyttes label til. Dermed tror jeg vi må la konsumenter rendre ut hele strukturen selv, slik at de kan koble på label og feilmeldinger til komponten.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T09:42:30Z
user: login: alexanbj
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T09:49:16Z
user: login: oscarcarlstrom
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T09:19:11Z
user: login: oscarcarlstrom
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T09:19:23Z
user: login: oscarcarlstrom
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T09:19:38Z
user: login: oscarcarlstrom
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T09:22:31Z
user: login: alexanbj
body: Vi nærmer oss noe veldig bra her!

En ting som ikke er tenkt på her nå er egentlig UU. React aria krever at tag-group er rendrer med enten label, aria-label eller aria-labelledby. Kan vi ta en huddle om dette?


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T09:40:22Z
user: login: gabrielwallin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T09:42:36Z
user: login: alexanbj
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T09:56:04Z
user: login: oscarcarlstrom
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:14:12Z
user: login: oscarcarlstrom
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:17:50Z
user: login: gabrielwallin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:18:25Z
user: login: oscarcarlstrom
body: Har sett litt nærmere på og testet Tags nå, og vi kan ikke bruke den sånn som den er nå:

1. Single/multiple selection funker ikke med skjermleser. Hovedproblemet er at det ikke er mulig å velge i det hele tatt med skjermleser. Det gis da naturlig nok heller ingen indikasjon på at de kan klikkes på heller.
2. Removable funker ikke med tastatur (dvs. er ikke mulig å klikke de vekk med tastatur)

Er litt usikker på om det er mulig å overstyre i RAC sine Tags, men verdt å se på kanskje? Også viktig å nevne at dette ikke er noe feil vi har gjort, men det er problemer i RAC.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:20:24Z
user: login: oscarcarlstrom
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:37:44Z
user: login: gabrielwallin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T11:55:41Z
user: login: alexanbj
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T13:57:21Z
user: login: alexanbj
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T14:06:16Z
user: login: alexanbj
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T14:28:56Z

Review comments:
diff_hunk: @@ -0,0 +1,246 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { type VariantProps, cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  Tag,
+  TagGroup,
+  TagList,
+} from 'react-aria-components';
+
+const chipVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+  ],
+  variants: {
+    /**
+     * The variant of the chip
+     * @default primary
+     */
+    variant: {
+      primary: '',
+      secondary: 'shadow-[inset_0_0_0_2px]',
+    },
+    /**
+     * The color variant of the chip
+     * @default green
+     */
+    color: {
+      green: 'data-[focus-visible]:outline-focus',
+      white:
+        'data-[focus-visible]:outline-focus data-[focus-visible]:outline-white',
+    },
+    /**
+     * Whether the chip is selected
+     * @default false
+     */
+    isSelected: {
+      true: '',
+      false: '',
+    },
+    /**
+     * Whether the chip is disabled
+     * @default false
+     */
+    isDisabled: {
+      true: 'cursor-not-allowed opacity-50',
+      false: '',
+    },
+    /**
+     * Whether the chip is removable
+     * @default false
+     */
+    isRemovable: {
+      true: 'cursor-pointer',
+      false: '',
+    },
+  },
+  compoundVariants: [
+    {
+      variant: 'primary',
+      color: 'green',
+      className: 'bg-green text-white hover:shadow-[inset_0_0_0_2px_#00524c]', //green-dark
+    },
+    {
+      variant: 'primary',
+      color: 'white',
+      className: 'bg-white text-black hover:shadow-[inset_0_0_0_2px_#bedfec]', //sky
+    },
+    {
+      variant: 'secondary',
+      color: 'green',
+      className:
+        'bg-white text-black shadow-green hover:shadow-green-dark active:shadow-green-dark',
+    },
+    {
+      variant: 'secondary',
+      color: 'white',
+      className:
+        'bg-transparent text-white shadow-white hover:shadow-sky active:shadow-sky',
+    },
+    {
+      color: 'green',
+      isSelected: true,
+      variant: 'primary',
+      className: 'bg-green-dark',
+    },
+    {
+      color: 'white',
+      isSelected: true,
+      variant: 'primary',
+      className: 'bg-sky',
+    },
+    {
+      color: 'green',
+      isSelected: true,
+      variant: 'secondary',
+      className: 'bg-green text-white',
+    },
+    {
+      color: 'white',
+      isSelected: true,
+      variant: 'secondary',
+      className: 'bg-white text-black',
+    },
+  ],
+  defaultVariants: {
+    color: 'green',
+    variant: 'primary',
+    isSelected: false,
+    isDisabled: false,
+    isRemovable: false,
+  },
+});
+
+const removeButtonVariants = cva({
+  base: [
+    'ml-1 flex items-center justify-center rounded-full p-0.5',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'hover:bg-black/10',
+  ],
+});
+
+export type ChipGroupProps = Omit<RACTagGroupProps, 'className'> & {
+  /**
+   * The selection mode of the chip group
+   * @default single
+   */
+  selectionMode?: 'single' | 'multiple';
+  /**
+   * CSS classes to apply to the chip group
+   */
+  className?: string;
+};
+
+export type ChipProps = Omit<RACTagProps, 'className'> & {
+  children: React.ReactNode;
+  /**
+   * The color variant of the chip
+   * @default green
+   */
+  color?: VariantProps<typeof chipVariants>['color'];
+  /**
+   * The variant of the chip
+   * @default primary
+   */
+  variant?: VariantProps<typeof chipVariants>['variant'];
+  /**
+   * The function to call when the chip is removed
+   */
+  onRemove?: (key: React.Key) => void;
+  /**
+   * CSS classes to apply to the chip
+   */
+  className?: string;
+  /**
+   * Whether the remove button should be hidden
+   * @default false
+   */
+  hideRemoveButton?: boolean;
+};
+
+/**
+ * A group component for Chip components that enables selection and organization of options.
+ */
+function ChipGroup(props: ChipGroupProps, ref: Ref<HTMLDivElement>) {
+  const { selectionMode = 'single', className, children, ...restProps } = props;
+
+  return (
+    <TagGroup
+      {...restProps}
+      ref={ref}
+      className={className}
+      selectionMode={selectionMode}
+    >
+      <TagList className="flex flex-wrap gap-2">{children}</TagList>
+    </TagGroup>
+  );
+}
+
+/**
+ * Interactive chip component for selections, filtering, and categorization.
+ */
+function Chip(props: ChipProps, ref: Ref<HTMLDivElement>) {
+  const {
+    color = 'green',
+    variant = 'primary',
+    onRemove,
+    className,
+    hideRemoveButton = false,
+    children,
+    ...restProps
+  } = props;
+
+  // Create a stable reference to the onRemove callback
+  const handleRemove = useCallback(() => {
+    if (onRemove && props.id && !props.isDisabled) {
+      onRemove(props.id);
+    }
+  }, [onRemove, props.id, props.isDisabled]);
+
+  const isRemovable = !!onRemove && !hideRemoveButton;
+
+  return (
+    <Tag
+      {...restProps}
+      ref={ref}
+      className={({ isSelected, isDisabled }) =>
+        chipVariants({
+          className,
+          color,
+          variant,
+          isSelected,
+          isDisabled,
+          isRemovable: false,
+        })
+      }
+    >
+      {({ isDisabled }) => (
+        <div className="flex items-center px-3 py-1.5">
+          <span className="flex items-center gap-1 align-middle">
+            {children}
+          </span>
+          {isRemovable && (
+            <Button
+              className={removeButtonVariants()}
+              onPress={handleRemove}
+              isDisabled={isDisabled}
+              aria-label="Remove"
path: packages/react/src/chip/chip.tsx
user: login: alexanbj
body: Om komponenten skal være slik, så må vi støtte/norsk svensk her. Men vi kan vente til Kristian er tilbake.
created_at: 2025-03-11T09:47:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,246 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { type VariantProps, cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  Tag,
+  TagGroup,
+  TagList,
+} from 'react-aria-components';
+
+const chipVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+  ],
+  variants: {
+    /**
+     * The variant of the chip
+     * @default primary
+     */
+    variant: {
+      primary: '',
+      secondary: 'shadow-[inset_0_0_0_2px]',
+    },
+    /**
+     * The color variant of the chip
+     * @default green
+     */
+    color: {
+      green: 'data-[focus-visible]:outline-focus',
+      white:
+        'data-[focus-visible]:outline-focus data-[focus-visible]:outline-white',
+    },
+    /**
+     * Whether the chip is selected
+     * @default false
+     */
+    isSelected: {
+      true: '',
+      false: '',
+    },
+    /**
+     * Whether the chip is disabled
+     * @default false
+     */
+    isDisabled: {
+      true: 'cursor-not-allowed opacity-50',
+      false: '',
+    },
+    /**
+     * Whether the chip is removable
+     * @default false
+     */
+    isRemovable: {
+      true: 'cursor-pointer',
+      false: '',
+    },
+  },
+  compoundVariants: [
+    {
+      variant: 'primary',
+      color: 'green',
+      className: 'bg-green text-white hover:shadow-[inset_0_0_0_2px_#00524c]', //green-dark
+    },
+    {
+      variant: 'primary',
+      color: 'white',
+      className: 'bg-white text-black hover:shadow-[inset_0_0_0_2px_#bedfec]', //sky
+    },
+    {
+      variant: 'secondary',
+      color: 'green',
+      className:
+        'bg-white text-black shadow-green hover:shadow-green-dark active:shadow-green-dark',
+    },
+    {
+      variant: 'secondary',
+      color: 'white',
+      className:
+        'bg-transparent text-white shadow-white hover:shadow-sky active:shadow-sky',
+    },
+    {
+      color: 'green',
+      isSelected: true,
+      variant: 'primary',
+      className: 'bg-green-dark',
+    },
+    {
+      color: 'white',
+      isSelected: true,
+      variant: 'primary',
+      className: 'bg-sky',
+    },
+    {
+      color: 'green',
+      isSelected: true,
+      variant: 'secondary',
+      className: 'bg-green text-white',
+    },
+    {
+      color: 'white',
+      isSelected: true,
+      variant: 'secondary',
+      className: 'bg-white text-black',
+    },
+  ],
+  defaultVariants: {
+    color: 'green',
+    variant: 'primary',
+    isSelected: false,
+    isDisabled: false,
+    isRemovable: false,
+  },
+});
+
+const removeButtonVariants = cva({
+  base: [
+    'ml-1 flex items-center justify-center rounded-full p-0.5',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'hover:bg-black/10',
+  ],
path: packages/react/src/chip/chip.tsx
user: login: alexanbj
body: Her er det ingen css klasser som er dynamiske, så denne burde ikke defineres opp med CVA.
created_at: 2025-03-11T09:49:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1 @@
+export * from './tag';
path: packages/react/src/tag/index.ts
user: login: alexanbj
body: kan vi kalle filen `tag-group` så den matcher react-aria?
created_at: 2025-03-14T08:22:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,148 @@
+import { Calendar, House } from '@obosbbl/grunnmuren-icons-react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { useState } from 'react';
+import type { Selection } from 'react-aria-components';
+
+import { Tag, TagGroup } from './tag';
+
+const meta: Meta<typeof TagGroup> = {
+  title: 'Tag | Chip',
path: packages/react/src/tag/tag.stories.tsx
user: login: alexanbj
body: ```suggestion
  title: 'TagGroup,
```

Tenker Chips blir noe vi skriver i GRunnmuren docen
created_at: 2025-03-14T08:22:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
+  variants: {
+    /**
+     * Whether the tag is selected
+     * @default false
+     */
+    isSelected: {
path: packages/react/src/tag/tag.tsx
user: login: alexanbj
body: Dette bør ikke være en variant. Skriv heller CSSen slik at den targeter `aria-selected`. Da trenger vi ikke important override heller.
created_at: 2025-03-14T08:23:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
+  variants: {
+    /**
+     * Whether the tag is selected
+     * @default false
+     */
+    isSelected: {
+      true: '!bg-sky !text-black',
+      false: '',
+    },
+    /**
+     * Whether the tag is removable
+     * @default false
+     */
+    isRemovable: {
+      true: 'cursor-pointer',
+      false: '',
+    },
+  },
+  defaultVariants: {
+    isSelected: false,
+    isRemovable: false,
+  },
+});
+
+const removeButtonStyling =
+  'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none flex w-full items-center px-3 py-1.5';
+
+export type TagGroupProps = Omit<RACTagGroupProps, 'className'> & {
+  /**
+   * The selection mode of the tag group
+   * @default single
+   */
+  selectionMode?: 'single' | 'multiple';
+  /**
+   * CSS classes to apply to the tag group
+   */
+  className?: string;
+};
+
+export type TagProps = Omit<RACTagProps, 'className'> & {
+  children: React.ReactNode;
+  /**
+   * The function to call when the tag is removed
+   */
+  onRemove?: (key: React.Key) => void;
+  /**
+   * CSS classes to apply to the tag
+   */
+  className?: string;
+  /**
+   * Whether the remove button should be hidden
+   * @default false
+   */
+  hideRemoveButton?: boolean;
+};
+
+/**
+ * A group component for Tag components that enables selection and organization of options.
+ */
+function TagGroup(props: TagGroupProps, ref: Ref<HTMLDivElement>) {
+  const { selectionMode = 'single', className, children, ...restProps } = props;
+
+  return (
+    <RACTagGroup
+      {...restProps}
+      ref={ref}
+      className={className}
+      selectionMode={selectionMode}
+    >
+      <TagList className="flex flex-wrap gap-2">{children}</TagList>
path: packages/react/src/tag/tag.tsx
user: login: alexanbj
body: Jeg synes ikke vi skal automatisk rendre denne, men heller eksportere den som en komponent. Det gjør det hele veldig mye mer composable for komponenten. Da kan de override stylingen av denne, eller rendre andre ting sammen med TagGroupen.
created_at: 2025-03-14T08:25:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
+  variants: {
+    /**
+     * Whether the tag is selected
+     * @default false
+     */
+    isSelected: {
+      true: '!bg-sky !text-black',
+      false: '',
+    },
+    /**
+     * Whether the tag is removable
+     * @default false
+     */
+    isRemovable: {
+      true: 'cursor-pointer',
+      false: '',
+    },
path: packages/react/src/tag/tag.tsx
user: login: alexanbj
body: cursor-pointer burde flyttes til base, da vi har cursor-pointer på alle klikkbare-ting.
created_at: 2025-03-14T08:27:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
+  variants: {
+    /**
+     * Whether the tag is selected
+     * @default false
+     */
+    isSelected: {
+      true: '!bg-sky !text-black',
+      false: '',
path: packages/react/src/tag/tag.tsx
user: login: oscarcarlstrom
body: ```suggestion
      false: 'bg-white text-black',
```
created_at: 2025-03-14T09:19:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
+  variants: {
+    /**
+     * Whether the tag is selected
+     * @default false
+     */
+    isSelected: {
+      true: '!bg-sky !text-black',
path: packages/react/src/tag/tag.tsx
user: login: oscarcarlstrom
body: ```suggestion
      true: '',
```
created_at: 2025-03-14T09:19:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
path: packages/react/src/tag/tag.tsx
user: login: oscarcarlstrom
body: ```suggestion
    'shadow-[inset_0_0_0_2px_#002169]',
```
created_at: 2025-03-14T09:19:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
+  variants: {
+    /**
+     * Whether the tag is selected
+     * @default false
+     */
+    isSelected: {
+      true: '!bg-sky !text-black',
+      false: '',
+    },
+    /**
+     * Whether the tag is removable
+     * @default false
+     */
+    isRemovable: {
+      true: 'cursor-pointer',
+      false: '',
+    },
+  },
+  defaultVariants: {
+    isSelected: false,
+    isRemovable: false,
+  },
+});
+
+const removeButtonStyling =
+  'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none flex w-full items-center px-3 py-1.5';
+
+export type TagGroupProps = Omit<RACTagGroupProps, 'className'> & {
+  /**
+   * The selection mode of the tag group
+   * @default single
+   */
+  selectionMode?: 'single' | 'multiple';
+  /**
+   * CSS classes to apply to the tag group
+   */
+  className?: string;
+};
+
+export type TagProps = Omit<RACTagProps, 'className'> & {
+  children: React.ReactNode;
+  /**
+   * The function to call when the tag is removed
+   */
+  onRemove?: (key: React.Key) => void;
+  /**
+   * CSS classes to apply to the tag
+   */
+  className?: string;
+  /**
+   * Whether the remove button should be hidden
+   * @default false
+   */
+  hideRemoveButton?: boolean;
+};
+
+/**
+ * A group component for Tag components that enables selection and organization of options.
+ */
+function TagGroup(props: TagGroupProps, ref: Ref<HTMLDivElement>) {
+  const { selectionMode = 'single', className, children, ...restProps } = props;
+
+  return (
+    <RACTagGroup
+      {...restProps}
+      ref={ref}
+      className={className}
+      selectionMode={selectionMode}
+    >
+      <TagList className="flex flex-wrap gap-2">{children}</TagList>
+    </RACTagGroup>
+  );
+}
+
+/**
+ * Interactive tag component for selections, filtering, and categorization.
+ */
+function Tag(props: TagProps, ref: Ref<HTMLDivElement>) {
+  const {
+    onRemove,
+    className,
+    hideRemoveButton = false,
+    children,
+    ...restProps
+  } = props;
+
+  // Create a stable reference to the onRemove callback
+  const handleRemove = useCallback(() => {
+    if (onRemove && props.id) {
+      onRemove(props.id);
+    }
+  }, [onRemove, props.id]);
+
+  const isRemovable = !!onRemove && !hideRemoveButton;
+
+  return (
+    <RACTag
+      {...restProps}
+      ref={ref}
+      className={({ isSelected }) =>
+        tagVariants({
+          className,
+          isSelected,
+          isRemovable,
+        })
+      }
+    >
+      {isRemovable ? (
+        <Button
+          className={removeButtonStyling}
+          onPress={handleRemove}
+          slot={'remove'}
+        >
+          <span className="flex items-center gap-1 align-middle">
+            {children}
+          </span>
+          <Close className="ml-1 h-4 w-4" />
+        </Button>
+      ) : (
+        <div className="flex items-center px-3 py-1.5">
+          <span className="flex items-center gap-1 align-middle">
+            {children}
+          </span>
+        </div>
+      )}
+    </RACTag>
+  );
+}
+
+const _TagGroup = forwardRef(TagGroup);
+const _Tag = forwardRef(Tag);
+
+export { _TagGroup as TagGroup, _Tag as Tag };
path: packages/react/src/tag/tag.tsx
user: login: oscarcarlstrom
body: ```suggestion
export { _TagGroup as UNSAFE_TagGroup, _Tag as UNSAFE_Tag };
```
created_at: 2025-03-14T09:22:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
path: packages/react/src/tag/tag.tsx
user: login: alexanbj
body: Jeg føler tag-en også trenger en slags hoverstate. Nå er det litt vanskelig å se at den er klikkbar
created_at: 2025-03-14T09:37:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
path: packages/react/src/tag/tag.tsx
user: login: gabrielwallin
body: Väldigt enig! Osäker på vad bara.. Underline kanske??
created_at: 2025-03-14T09:42:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
+  variants: {
+    /**
+     * Whether the tag is selected
+     * @default false
+     */
+    isSelected: {
+      true: '!bg-sky !text-black',
+      false: '',
+    },
+    /**
+     * Whether the tag is removable
+     * @default false
+     */
+    isRemovable: {
+      true: 'cursor-pointer',
+      false: '',
+    },
+  },
+  defaultVariants: {
+    isSelected: false,
+    isRemovable: false,
+  },
+});
+
+const removeButtonStyling =
+  'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none flex w-full items-center px-3 py-1.5';
+
+export type TagGroupProps = Omit<RACTagGroupProps, 'className'> & {
+  /**
+   * The selection mode of the tag group
+   * @default single
+   */
+  selectionMode?: 'single' | 'multiple';
+  /**
+   * CSS classes to apply to the tag group
+   */
+  className?: string;
+};
+
+export type TagProps = Omit<RACTagProps, 'className'> & {
+  children: React.ReactNode;
+  /**
+   * The function to call when the tag is removed
+   */
+  onRemove?: (key: React.Key) => void;
+  /**
+   * CSS classes to apply to the tag
+   */
+  className?: string;
+  /**
+   * Whether the remove button should be hidden
+   * @default false
+   */
+  hideRemoveButton?: boolean;
+};
+
+/**
+ * A group component for Tag components that enables selection and organization of options.
+ */
+function TagGroup(props: TagGroupProps, ref: Ref<HTMLDivElement>) {
+  const { selectionMode = 'single', className, children, ...restProps } = props;
+
+  return (
+    <RACTagGroup
+      {...restProps}
+      ref={ref}
+      className={className}
+      selectionMode={selectionMode}
+    >
+      <TagList className="flex flex-wrap gap-2">{children}</TagList>
+    </RACTagGroup>
+  );
+}
+
+/**
+ * Interactive tag component for selections, filtering, and categorization.
+ */
+function Tag(props: TagProps, ref: Ref<HTMLDivElement>) {
+  const {
+    onRemove,
+    className,
+    hideRemoveButton = false,
+    children,
+    ...restProps
+  } = props;
+
+  // Create a stable reference to the onRemove callback
+  const handleRemove = useCallback(() => {
+    if (onRemove && props.id) {
+      onRemove(props.id);
+    }
+  }, [onRemove, props.id]);
+
+  const isRemovable = !!onRemove && !hideRemoveButton;
+
+  return (
+    <RACTag
+      {...restProps}
+      ref={ref}
+      className={({ isSelected }) =>
+        tagVariants({
+          className,
+          isSelected,
+          isRemovable,
+        })
+      }
+    >
+      {isRemovable ? (
+        <Button
+          className={removeButtonStyling}
+          onPress={handleRemove}
+          slot={'remove'}
+        >
+          <span className="flex items-center gap-1 align-middle">
+            {children}
+          </span>
+          <Close className="ml-1 h-4 w-4" />
+        </Button>
+      ) : (
+        <div className="flex items-center px-3 py-1.5">
+          <span className="flex items-center gap-1 align-middle">
+            {children}
+          </span>
+        </div>
+      )}
+    </RACTag>
+  );
+}
+
+const _TagGroup = forwardRef(TagGroup);
+const _Tag = forwardRef(Tag);
path: packages/react/src/tag/tag.tsx
user: login: alexanbj
body: Skal vi bare slutte å forwardReffe for nye komponenter? Det trengs ikke lenger i React 19, og alle teamene jeg vet om er over https://react.dev/blog/2024/12/05/react-19#ref-as-a-prop
created_at: 2025-03-14T09:56:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
+  variants: {
+    /**
+     * Whether the tag is selected
+     * @default false
+     */
+    isSelected: {
+      true: '!bg-sky !text-black',
+      false: '',
+    },
+    /**
+     * Whether the tag is removable
+     * @default false
+     */
+    isRemovable: {
+      true: 'cursor-pointer',
+      false: '',
+    },
+  },
+  defaultVariants: {
+    isSelected: false,
+    isRemovable: false,
+  },
+});
+
+const removeButtonStyling =
+  'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none flex w-full items-center px-3 py-1.5';
+
+export type TagGroupProps = Omit<RACTagGroupProps, 'className'> & {
+  /**
+   * The selection mode of the tag group
+   * @default single
+   */
+  selectionMode?: 'single' | 'multiple';
+  /**
+   * CSS classes to apply to the tag group
+   */
+  className?: string;
+};
+
+export type TagProps = Omit<RACTagProps, 'className'> & {
+  children: React.ReactNode;
+  /**
+   * The function to call when the tag is removed
+   */
+  onRemove?: (key: React.Key) => void;
+  /**
+   * CSS classes to apply to the tag
+   */
+  className?: string;
+  /**
+   * Whether the remove button should be hidden
+   * @default false
+   */
+  hideRemoveButton?: boolean;
+};
+
+/**
+ * A group component for Tag components that enables selection and organization of options.
+ */
+function TagGroup(props: TagGroupProps, ref: Ref<HTMLDivElement>) {
+  const { selectionMode = 'single', className, children, ...restProps } = props;
+
+  return (
+    <RACTagGroup
+      {...restProps}
+      ref={ref}
+      className={className}
+      selectionMode={selectionMode}
+    >
+      <TagList className="flex flex-wrap gap-2">{children}</TagList>
+    </RACTagGroup>
+  );
+}
+
+/**
+ * Interactive tag component for selections, filtering, and categorization.
+ */
+function Tag(props: TagProps, ref: Ref<HTMLDivElement>) {
+  const {
+    onRemove,
+    className,
+    hideRemoveButton = false,
+    children,
+    ...restProps
+  } = props;
+
+  // Create a stable reference to the onRemove callback
+  const handleRemove = useCallback(() => {
+    if (onRemove && props.id) {
+      onRemove(props.id);
+    }
+  }, [onRemove, props.id]);
+
+  const isRemovable = !!onRemove && !hideRemoveButton;
path: packages/react/src/tag/tag.tsx
user: login: oscarcarlstrom
body: Den här tyckte jag var lite konstig. För att `isRemovable` ska bli `true` så får inte `hideRemoveButton` vara `true`. Vad är då poängen med `hideRemoveButton`?
created_at: 2025-03-14T11:14:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
+  variants: {
+    /**
+     * Whether the tag is selected
+     * @default false
+     */
+    isSelected: {
+      true: '!bg-sky !text-black',
+      false: '',
+    },
+    /**
+     * Whether the tag is removable
+     * @default false
+     */
+    isRemovable: {
+      true: 'cursor-pointer',
+      false: '',
+    },
+  },
+  defaultVariants: {
+    isSelected: false,
+    isRemovable: false,
+  },
+});
+
+const removeButtonStyling =
+  'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none flex w-full items-center px-3 py-1.5';
+
+export type TagGroupProps = Omit<RACTagGroupProps, 'className'> & {
+  /**
+   * The selection mode of the tag group
+   * @default single
+   */
+  selectionMode?: 'single' | 'multiple';
+  /**
+   * CSS classes to apply to the tag group
+   */
+  className?: string;
+};
+
+export type TagProps = Omit<RACTagProps, 'className'> & {
+  children: React.ReactNode;
+  /**
+   * The function to call when the tag is removed
+   */
+  onRemove?: (key: React.Key) => void;
+  /**
+   * CSS classes to apply to the tag
+   */
+  className?: string;
+  /**
+   * Whether the remove button should be hidden
+   * @default false
+   */
+  hideRemoveButton?: boolean;
+};
+
+/**
+ * A group component for Tag components that enables selection and organization of options.
+ */
+function TagGroup(props: TagGroupProps, ref: Ref<HTMLDivElement>) {
+  const { selectionMode = 'single', className, children, ...restProps } = props;
+
+  return (
+    <RACTagGroup
+      {...restProps}
+      ref={ref}
+      className={className}
+      selectionMode={selectionMode}
+    >
+      <TagList className="flex flex-wrap gap-2">{children}</TagList>
+    </RACTagGroup>
+  );
+}
+
+/**
+ * Interactive tag component for selections, filtering, and categorization.
+ */
+function Tag(props: TagProps, ref: Ref<HTMLDivElement>) {
+  const {
+    onRemove,
+    className,
+    hideRemoveButton = false,
+    children,
+    ...restProps
+  } = props;
+
+  // Create a stable reference to the onRemove callback
+  const handleRemove = useCallback(() => {
+    if (onRemove && props.id) {
+      onRemove(props.id);
+    }
+  }, [onRemove, props.id]);
+
+  const isRemovable = !!onRemove && !hideRemoveButton;
+
+  return (
+    <RACTag
+      {...restProps}
+      ref={ref}
+      className={({ isSelected }) =>
+        tagVariants({
+          className,
+          isSelected,
+          isRemovable,
+        })
+      }
+    >
+      {isRemovable ? (
+        <Button
+          className={removeButtonStyling}
+          onPress={handleRemove}
+          slot={'remove'}
+        >
+          <span className="flex items-center gap-1 align-middle">
+            {children}
+          </span>
+          <Close className="ml-1 h-4 w-4" />
+        </Button>
+      ) : (
+        <div className="flex items-center px-3 py-1.5">
+          <span className="flex items-center gap-1 align-middle">
+            {children}
+          </span>
+        </div>
+      )}
+    </RACTag>
+  );
+}
+
+const _TagGroup = forwardRef(TagGroup);
+const _Tag = forwardRef(Tag);
path: packages/react/src/tag/tag.tsx
user: login: oscarcarlstrom
body: Ja enig, jeg har prøvd å styre unna det. Men har vært noen ganger da typingen i RAC ikke har vært oppdatert så jeg har måttet bruke fordwardRefs (typisk i `useContextProps` osv). Kan hende det har blitt bedre i det siste for så vidt

created_at: 2025-03-14T11:17:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
+  variants: {
+    /**
+     * Whether the tag is selected
+     * @default false
+     */
+    isSelected: {
+      true: '!bg-sky !text-black',
+      false: '',
+    },
+    /**
+     * Whether the tag is removable
+     * @default false
+     */
+    isRemovable: {
+      true: 'cursor-pointer',
+      false: '',
+    },
+  },
+  defaultVariants: {
+    isSelected: false,
+    isRemovable: false,
+  },
+});
+
+const removeButtonStyling =
+  'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none flex w-full items-center px-3 py-1.5';
+
+export type TagGroupProps = Omit<RACTagGroupProps, 'className'> & {
+  /**
+   * The selection mode of the tag group
+   * @default single
+   */
+  selectionMode?: 'single' | 'multiple';
+  /**
+   * CSS classes to apply to the tag group
+   */
+  className?: string;
+};
+
+export type TagProps = Omit<RACTagProps, 'className'> & {
+  children: React.ReactNode;
+  /**
+   * The function to call when the tag is removed
+   */
+  onRemove?: (key: React.Key) => void;
+  /**
+   * CSS classes to apply to the tag
+   */
+  className?: string;
+  /**
+   * Whether the remove button should be hidden
+   * @default false
+   */
+  hideRemoveButton?: boolean;
+};
+
+/**
+ * A group component for Tag components that enables selection and organization of options.
+ */
+function TagGroup(props: TagGroupProps, ref: Ref<HTMLDivElement>) {
+  const { selectionMode = 'single', className, children, ...restProps } = props;
+
+  return (
+    <RACTagGroup
+      {...restProps}
+      ref={ref}
+      className={className}
+      selectionMode={selectionMode}
+    >
+      <TagList className="flex flex-wrap gap-2">{children}</TagList>
+    </RACTagGroup>
+  );
+}
+
+/**
+ * Interactive tag component for selections, filtering, and categorization.
+ */
+function Tag(props: TagProps, ref: Ref<HTMLDivElement>) {
+  const {
+    onRemove,
+    className,
+    hideRemoveButton = false,
+    children,
+    ...restProps
+  } = props;
+
+  // Create a stable reference to the onRemove callback
+  const handleRemove = useCallback(() => {
+    if (onRemove && props.id) {
+      onRemove(props.id);
+    }
+  }, [onRemove, props.id]);
+
+  const isRemovable = !!onRemove && !hideRemoveButton;
path: packages/react/src/tag/tag.tsx
user: login: gabrielwallin
body: God poäng 
created_at: 2025-03-14T11:18:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
path: packages/react/src/tag/tag.tsx
user: login: oscarcarlstrom
body: Eller bakgrunnsfarge?
created_at: 2025-03-14T11:37:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,151 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import { type Ref, forwardRef, useCallback } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  type TagProps as RACTagProps,
+  TagList,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-default items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    'bg-white text-black shadow-[inset_0_0_0_2px_#002169]',
+  ],
path: packages/react/src/tag/tag.tsx
user: login: gabrielwallin
body: Fixade hover med sky!
created_at: 2025-03-17T11:55:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,167 @@
+import { Calendar, House } from '@obosbbl/grunnmuren-icons-react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { useState } from 'react';
+import { Label, type Selection } from 'react-aria-components';
+
+import { Description } from '../label';
+import { UNSAFE_Tag, UNSAFE_TagGroup, UNSAFE_TagList } from './tag-group';
path: packages/react/src/tag-group/tag-group.stories.tsx
user: login: alexanbj
body: Kan vi rename disse som en del av importen her? Typ `import { UNSAFE_Tag as Tag }`, så når de ikke lenger er unsafe kan fikse det kun i importen.
created_at: 2025-03-17T12:25:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,130 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import type { RefAttributes } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  TagList as RACTagList,
+  type TagListProps as RACTagListProps,
+  type TagProps as RACTagProps,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-pointer items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    //Focus
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    //Border
+    'shadow-[inset_0_0_0_2px_#002169]',
+    //Backgrounds
+    "hover:!bg-sky bg-white text-black aria-selected:bg-sky-light [&:has([slot='remove'])]:bg-sky-light",
path: packages/react/src/tag-group/tag-group.tsx
user: login: alexanbj
body: Burde vi ikek heller bruke bruke data-hovered og data-allows-removing her? https://react-spectrum.adobe.com/react-aria/TagGroup.html#tag-1
created_at: 2025-03-17T12:26:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,130 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import type { RefAttributes } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  TagList as RACTagList,
+  type TagListProps as RACTagListProps,
+  type TagProps as RACTagProps,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-pointer items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    //Focus
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    //Border
+    'shadow-[inset_0_0_0_2px_#002169]',
+    //Backgrounds
+    "hover:!bg-sky bg-white text-black aria-selected:bg-sky-light [&:has([slot='remove'])]:bg-sky-light",
+  ],
+});
+
+const removeButtonStyling =
+  'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none flex w-full items-center px-3 py-1.5';
+
+export type TagGroupProps = Omit<RACTagGroupProps, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    /**
+     * The selection mode of the tag group
+     * @default single
+     */
+    selectionMode?: 'single' | 'multiple';
+    /**
+     * CSS classes to apply to the tag group
+     */
+    className?: string;
+    /**
+     * The function to call when the tag is removed
+     */
+    onRemove?: (key: React.Key) => void;
+  };
+
+//The usage of <object> here could probably be replaced with a generic for more type safety in usage
+export type TagListProps = Omit<RACTagListProps<object>, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    /**
+     * CSS classes to apply to the tag list
+     */
+    className?: string;
+  };
+
+export type TagProps = Omit<RACTagProps, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    children: React.ReactNode;
+    /**
+     * CSS classes to apply to the tag
+     */
+    className?: string;
+  };
+
+/**
+ * A group component for Tag components that enables selection and organization of options.
+ */
+function TagGroup(props: TagGroupProps) {
+  const { onRemove, selectionMode, className, children, ...restProps } = props;
+
+  return (
+    <RACTagGroup
+      {...restProps}
+      className={className}
+      selectionMode={selectionMode}
+      onRemove={onRemove}
+    >
+      {children}
+    </RACTagGroup>
+  );
+}
+
+/**
+ * A container component for Tag components within a TagGroup.
+ */
+function TagList(props: TagListProps) {
+  const { className = 'flex flex-wrap gap-2', children, ...restProps } = props;
path: packages/react/src/tag-group/tag-group.tsx
user: login: alexanbj
body: Her må vi ha dette som default className, så bruke `cx` for å slå sammen om det kommer en custom className prop. Som konsument forventer jeg ikke at default klassene plutselig forsvinner om jeg legger til en custom className
created_at: 2025-03-17T12:28:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,130 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import type { RefAttributes } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  TagList as RACTagList,
+  type TagListProps as RACTagListProps,
+  type TagProps as RACTagProps,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-pointer items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    //Focus
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    //Border
+    'shadow-[inset_0_0_0_2px_#002169]',
+    //Backgrounds
+    "hover:!bg-sky bg-white text-black aria-selected:bg-sky-light [&:has([slot='remove'])]:bg-sky-light",
+  ],
+});
+
+const removeButtonStyling =
+  'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none flex w-full items-center px-3 py-1.5';
+
+export type TagGroupProps = Omit<RACTagGroupProps, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    /**
+     * The selection mode of the tag group
+     * @default single
+     */
+    selectionMode?: 'single' | 'multiple';
+    /**
+     * CSS classes to apply to the tag group
+     */
+    className?: string;
+    /**
+     * The function to call when the tag is removed
+     */
+    onRemove?: (key: React.Key) => void;
+  };
+
+//The usage of <object> here could probably be replaced with a generic for more type safety in usage
+export type TagListProps = Omit<RACTagListProps<object>, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    /**
+     * CSS classes to apply to the tag list
+     */
+    className?: string;
+  };
+
+export type TagProps = Omit<RACTagProps, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    children: React.ReactNode;
+    /**
+     * CSS classes to apply to the tag
+     */
+    className?: string;
+  };
+
+/**
+ * A group component for Tag components that enables selection and organization of options.
+ */
+function TagGroup(props: TagGroupProps) {
+  const { onRemove, selectionMode, className, children, ...restProps } = props;
+
+  return (
+    <RACTagGroup
+      {...restProps}
+      className={className}
+      selectionMode={selectionMode}
+      onRemove={onRemove}
+    >
+      {children}
+    </RACTagGroup>
+  );
+}
+
+/**
+ * A container component for Tag components within a TagGroup.
+ */
+function TagList(props: TagListProps) {
+  const { className = 'flex flex-wrap gap-2', children, ...restProps } = props;
+
+  return (
+    <RACTagList {...restProps} className={className}>
+      {children}
+    </RACTagList>
+  );
+}
+
+/**
+ * Interactive tag component for selections, filtering, and categorization.
+ */
+function Tag(props: TagProps) {
+  const { className, children, ...restProps } = props;
+
+  const spanStyling = 'flex items-center gap-1 align-middle';
+
+  return (
+    <RACTag
+      {...restProps}
+      className={tagVariants({
+        className,
+      })}
+    >
+      {({ allowsRemoving }) =>
+        allowsRemoving ? (
+          <Button className={removeButtonStyling} slot={'remove'}>
path: packages/react/src/tag-group/tag-group.tsx
user: login: alexanbj
body: ```suggestion
          <Button className={removeButtonStyling} slot="remove">
```
created_at: 2025-03-17T12:30:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,130 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva } from 'cva';
+import type { RefAttributes } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  TagList as RACTagList,
+  type TagListProps as RACTagListProps,
+  type TagProps as RACTagProps,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-pointer items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    //Focus
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    //Border
+    'shadow-[inset_0_0_0_2px_#002169]',
+    //Backgrounds
+    "hover:!bg-sky bg-white text-black aria-selected:bg-sky-light [&:has([slot='remove'])]:bg-sky-light",
+  ],
+});
+
+const removeButtonStyling =
+  'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none flex w-full items-center px-3 py-1.5';
+
+export type TagGroupProps = Omit<RACTagGroupProps, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    /**
+     * The selection mode of the tag group
+     * @default single
+     */
+    selectionMode?: 'single' | 'multiple';
+    /**
+     * CSS classes to apply to the tag group
+     */
+    className?: string;
+    /**
+     * The function to call when the tag is removed
+     */
+    onRemove?: (key: React.Key) => void;
+  };
+
+//The usage of <object> here could probably be replaced with a generic for more type safety in usage
+export type TagListProps = Omit<RACTagListProps<object>, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    /**
+     * CSS classes to apply to the tag list
+     */
+    className?: string;
+  };
+
+export type TagProps = Omit<RACTagProps, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    children: React.ReactNode;
+    /**
+     * CSS classes to apply to the tag
+     */
+    className?: string;
+  };
+
+/**
+ * A group component for Tag components that enables selection and organization of options.
+ */
+function TagGroup(props: TagGroupProps) {
+  const { onRemove, selectionMode, className, children, ...restProps } = props;
+
+  return (
+    <RACTagGroup
+      {...restProps}
+      className={className}
+      selectionMode={selectionMode}
+      onRemove={onRemove}
+    >
+      {children}
+    </RACTagGroup>
+  );
+}
+
+/**
+ * A container component for Tag components within a TagGroup.
+ */
+function TagList(props: TagListProps) {
+  const { className = 'flex flex-wrap gap-2', children, ...restProps } = props;
+
+  return (
+    <RACTagList {...restProps} className={className}>
+      {children}
+    </RACTagList>
+  );
+}
+
+/**
+ * Interactive tag component for selections, filtering, and categorization.
+ */
+function Tag(props: TagProps) {
+  const { className, children, ...restProps } = props;
+
+  const spanStyling = 'flex items-center gap-1 align-middle';
+
+  return (
+    <RACTag
+      {...restProps}
+      className={tagVariants({
+        className,
+      })}
+    >
+      {({ allowsRemoving }) =>
+        allowsRemoving ? (
+          <Button className={removeButtonStyling} slot={'remove'}>
+            <span className={spanStyling}>{children}</span>
+            <Close className="ml-1 h-4 w-4" />
path: packages/react/src/tag-group/tag-group.tsx
user: login: alexanbj
body: Er det riktig at vi spesifiserer størrelse på close-ikonet, men om brukeren sender med et eget ikon får det en annen størrelse?
created_at: 2025-03-17T12:32:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,143 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva, cx } from 'cva';
+import type { RefAttributes } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  TagList as RACTagList,
+  type TagListProps as RACTagListProps,
+  type TagProps as RACTagProps,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-pointer items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    //Focus
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    //Border
+    'shadow-[inset_0_0_0_2px_#002169]',
+    //Backgrounds
+    'data-[hovered]:!bg-sky bg-white text-black aria-selected:bg-sky-light data-[allows-removing]:bg-sky-light',
+    //Icons
+    '[&_svg]:h-4 [&_svg]:w-4',
+  ],
+});
+
+export type TagGroupProps = Omit<RACTagGroupProps, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    /**
+     * CSS classes to apply to the tag group
+     */
+    className?: string;
+    /**
+     * The function to call when the tag is removed
+     */
+    onRemove?: (key: React.Key) => void;
+
+    /**
+     * The selection mode for the tag group
+     * Override this so that "none" isn't an option
+     * @default "single"
+     */
+    selectionMode?: 'single' | 'multiple';
+  };
+
+//The usage of <object> here could probably be replaced with a generic for more type safety in usage
+export type TagListProps = Omit<RACTagListProps<object>, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    /**
+     * CSS classes to apply to the tag list
+     */
+    className?: string;
+  };
+
+export type TagProps = Omit<RACTagProps, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    children: React.ReactNode;
+    /**
+     * CSS classes to apply to the tag
+     */
+    className?: string;
+  };
+
+/**
+ * A group component for Tag components that enables selection and organization of options.
+ */
+function TagGroup(props: TagGroupProps) {
+  const {
+    onRemove,
+    selectionMode = 'single',
+    className,
+    children,
+    ...restProps
+  } = props;
+
+  return (
+    <RACTagGroup
+      {...restProps}
+      className={className}
+      selectionMode={onRemove ? 'none' : selectionMode}
+      onRemove={onRemove}
+    >
+      {children}
+    </RACTagGroup>
+  );
+}
+
+/**
+ * A container component for Tag components within a TagGroup.
+ */
+function TagList(props: TagListProps) {
+  const { className = 'flex flex-wrap gap-2', children, ...restProps } = props;
path: packages/react/src/tag-group/tag-group.tsx
user: login: alexanbj
body: Glemt å rydde opp på linje 93 etter at det ble lagt til støtte for å kombinere className?
created_at: 2025-03-18T13:50:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,143 @@
+import { Close } from '@obosbbl/grunnmuren-icons-react';
+import { cva, cx } from 'cva';
+import type { RefAttributes } from 'react';
+import {
+  Button,
+  Tag as RACTag,
+  TagGroup as RACTagGroup,
+  type TagGroupProps as RACTagGroupProps,
+  TagList as RACTagList,
+  type TagListProps as RACTagListProps,
+  type TagProps as RACTagProps,
+} from 'react-aria-components';
+
+const tagVariants = cva({
+  base: [
+    'inline-flex cursor-pointer items-center gap-2 rounded-lg font-medium text-sm transition-colors duration-200',
+    //Focus
+    'focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none',
+    //Border
+    'shadow-[inset_0_0_0_2px_#002169]',
+    //Backgrounds
+    'data-[hovered]:!bg-sky bg-white text-black aria-selected:bg-sky-light data-[allows-removing]:bg-sky-light',
+    //Icons
+    '[&_svg]:h-4 [&_svg]:w-4',
+  ],
+});
+
+export type TagGroupProps = Omit<RACTagGroupProps, 'className'> &
+  RefAttributes<HTMLDivElement> & {
+    /**
+     * CSS classes to apply to the tag group
+     */
+    className?: string;
+    /**
+     * The function to call when the tag is removed
+     */
+    onRemove?: (key: React.Key) => void;
+
+    /**
+     * The selection mode for the tag group
+     * Override this so that "none" isn't an option
path: packages/react/src/tag-group/tag-group.tsx
user: login: alexanbj
body: Kan vi slette "Override this so....". Det lekker ut til konsumenten som en del av dokumentasjonen for prop. Føles internt ut.
created_at: 2025-03-18T14:04:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,171 @@
+import { Calendar, House } from '@obosbbl/grunnmuren-icons-react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { useState } from 'react';
+import { Label, type Selection } from 'react-aria-components';
+
+import { Description } from '../label';
+import {
+  UNSAFE_Tag as Tag,
+  UNSAFE_TagGroup as TagGroup,
+  UNSAFE_TagList as TagList,
+} from './tag-group';
+
+const meta: Meta<typeof TagGroup> = {
+  title: 'TagGroup',
+  component: TagGroup,
+  parameters: {
+    layout: 'fullscreen',
+  },
+};
+
+export default meta;
+
+type Story = StoryObj<typeof TagGroup>;
+
+export const Default: Story = {
+  args: {
+    selectionMode: 'single',
+  },
+  render: (props) => (
+    <div className="p-6">
+      <TagGroup {...props}>
path: packages/react/src/tag-group/tag-group.stories.tsx
user: login: alexanbj
body: Kan vi passe på at alle eksemplene i storybooken er universelt utformet, med aria-label for de eksemplene som ikke har det? Nå kommer det warnings i console pga manglende label, aria-label eller aria-labelledby
created_at: 2025-03-18T14:06:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-03-10T15:03:24Z
body: ###  🦋  Changeset detected

Latest commit: ecc61249dba33b3bc7d1e9cc09af0bffb4ef1f2c

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name                      | Type  |
| ------------------------- | ----- |
| @obosbbl/grunnmuren-react | Minor |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/code-obos/grunnmuren/new/chips?filename=.changeset/shiny-beds-give.md&value=---%0A%22%40obosbbl%2Fgrunnmuren-react%22%3A%20patch%0A---%0A%0ATagGroup%0A)


author_association: NONE
user: login: oscarcarlstrom
created_at: 2025-03-14T11:37:08Z
body: > Har sett litt nærmere på og testet Tags nå, og vi kan ikke bruke den sånn som den er nå:
> 
> 1. Single/multiple selection funker ikke med skjermleser. Dette er fordi den er satt opp med role="grid", uten noen klikkbare kontroller. Hovedproblemet er at det ikke er mulig å velge i det hele tatt med skjermleser. Det gis da naturlig nok heller ingen indikasjon på at de kan klikkes på heller.
> 2. Removable funker ikke med tastatur (dvs. er ikke mulig å klikke de vekk med tastatur)
> 
> Er litt usikker på om det er mulig å overstyre i RAC sine Tags, men verdt å se på kanskje? Også viktig å nevne at dette ikke er noe feil vi har gjort, men det er problemer i RAC.

Ser ut som vi kan løse pkt. 2 gjennom å flytte `onRemove`-handleren til `TagGroup`.
author_association: CONTRIBUTOR
user: login: oscarcarlstrom
created_at: 2025-03-17T07:32:30Z
body: > Har sett litt nærmere på og testet Tags nå, og vi kan ikke bruke den sånn som den er nå:
> 
> 1. Single/multiple selection funker ikke med skjermleser. Hovedproblemet er at det ikke er mulig å velge i det hele tatt med skjermleser. Det gis da naturlig nok heller ingen indikasjon på at de kan klikkes på heller.
> 2. Removable funker ikke med tastatur (dvs. er ikke mulig å klikke de vekk med tastatur)
> 
> Er litt usikker på om det er mulig å overstyre i RAC sine Tags, men verdt å se på kanskje? Også viktig å nevne at dette ikke er noe feil vi har gjort, men det er problemer i RAC.

Nr. 1 her gjelder visst kun i chrome. Så virker som det er en bug i Chrome eller VoiceOver som gjør at dette ikke funker. Funker fint i Safari.
author_association: CONTRIBUTOR