diff --git a/.changeset/spotty-oranges-guess.md b/.changeset/spotty-oranges-guess.md
new file mode 100644
index 0000000000..1415032986
--- /dev/null
+++ b/.changeset/spotty-oranges-guess.md
@@ -0,0 +1,6 @@
+---
+"@digdir/designsystemet-css": patch
+"@digdir/designsystemet-react": patch
+---
+
+Avatar: new component
diff --git a/packages/css/avatar.css b/packages/css/avatar.css
new file mode 100644
index 0000000000..b0329102ce
--- /dev/null
+++ b/packages/css/avatar.css
@@ -0,0 +1,98 @@
+.ds-avatar {
+ --dsc-avatar-background: var(--ds-color-accent-base-default);
+ --dsc-avatar-color: var(--ds-color-accent-contrast-default);
+ --dsc-avatar-size: var(--ds-sizing-12);
+ --dsc-avatar-padding: var(--ds-spacing-2);
+ --dsc-avatar-border-radius: 50%;
+
+ background: var(--dsc-avatar-background);
+ height: var(--dsc-avatar-size);
+ aspect-ratio: 1 / 1;
+ color: var(--dsc-avatar-color);
+ border-radius: var(--dsc-avatar-border-radius);
+ overflow: hidden;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ user-select: none;
+ text-transform: uppercase;
+ text-decoration: none;
+
+ &:not(:has(> img)) {
+ padding: var(--dsc-avatar-padding);
+ }
+
+ & img {
+ object-fit: cover;
+ width: 100%;
+ height: 100%;
+ }
+
+ & svg {
+ max-width: 100%;
+ max-height: 100%;
+ }
+
+ &:empty::before {
+ content: '';
+ width: 100%;
+ height: 100%;
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ background-color: currentcolor;
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' fill='none' viewBox='0 0 24 24' focusable='false' role='img'%3E%3Cpath fill='currentColor' fill-rule='evenodd' d='M8.25 7.5a3.75 3.75 0 1 1 7.5 0 3.75 3.75 0 0 1-7.5 0M12 2.25a5.25 5.25 0 1 0 0 10.5 5.25 5.25 0 0 0 0-10.5M8.288 17.288A5.25 5.25 0 0 1 17.25 21a.75.75 0 0 0 1.5 0 6.75 6.75 0 0 0-13.5 0 .75.75 0 0 0 1.5 0 5.25 5.25 0 0 1 1.538-3.712' clip-rule='evenodd'%3E%3C/path%3E%3C/svg%3E");
+ }
+
+ &[data-ds-variant='circle'] {
+ --dsc-avatar-border-radius: var(--ds-border-radius-full);
+ }
+
+ &[data-ds-variant='square'] {
+ --dsc-avatar-border-radius: var(--ds-border-radius-sm);
+ }
+
+ &[data-ds-color='accent'] {
+ --dsc-avatar-background: var(--ds-color-accent-base-default);
+ --dsc-avatar-color: var(--ds-color-accent-contrast-default);
+ }
+
+ &[data-ds-color='neutral'] {
+ --dsc-avatar-background: var(--ds-color-neutral-base-default);
+ --dsc-avatar-color: var(--ds-color-neutral-contrast-default);
+ }
+
+ &[data-ds-color='brand1'] {
+ --dsc-avatar-background: var(--ds-color-brand1-base-default);
+ --dsc-avatar-color: var(--ds-color-brand1-contrast-default);
+ }
+
+ &[data-ds-color='brand2'] {
+ --dsc-avatar-background: var(--ds-color-brand2-base-default);
+ --dsc-avatar-color: var(--ds-color-brand2-contrast-default);
+ }
+
+ &[data-ds-color='brand3'] {
+ --dsc-avatar-background: var(--ds-color-brand3-base-default);
+ --dsc-avatar-color: var(--ds-color-brand3-contrast-default);
+ }
+
+ &[data-ds-size='xs'] {
+ --dsc-avatar-size: var(--ds-sizing-7);
+ --dsc-avatar-padding: var(--ds-spacing-1);
+ }
+
+ &[data-ds-size='sm'] {
+ --dsc-avatar-size: var(--ds-sizing-9);
+ --dsc-avatar-padding: var(--ds-spacing-1);
+ }
+
+ &[data-ds-size='md'] {
+ --dsc-avatar-size: var(--ds-sizing-12);
+ --dsc-avatar-padding: var(--ds-spacing-2);
+ }
+
+ &[data-ds-size='lg'] {
+ --dsc-avatar-size: var(--ds-sizing-15);
+ --dsc-avatar-padding: var(--ds-spacing-2);
+ }
+}
diff --git a/packages/css/index.css b/packages/css/index.css
index df0f39837d..ef900c63f0 100644
--- a/packages/css/index.css
+++ b/packages/css/index.css
@@ -44,3 +44,4 @@
@import url('./combobox.css') layer(ds.components);
@import url('./breadcrumbs.css') layer(ds.components);
@import url('./badge.css') layer(ds.components);
+@import url('./avatar.css') layer(ds.components);
diff --git a/packages/react/src/components/Avatar/Avatar.mdx b/packages/react/src/components/Avatar/Avatar.mdx
new file mode 100644
index 0000000000..7160500dd6
--- /dev/null
+++ b/packages/react/src/components/Avatar/Avatar.mdx
@@ -0,0 +1,75 @@
+import { Meta, Canvas, Controls, Primary } from '@storybook/blocks';
+
+import * as AvatarStories from './Avatar.stories';
+
+
+
+# Avatar
+
+`Avatar` er en komponent som viser et bilde, initialer eller ikon for en bruker eller profil.
+
+
+
+
+## Bruk
+
+```tsx
+import { Avatar } from '@digdir/designsystemet-react';
+
+/* Med standard brukerikon */
+
+
+/* Med initialer */
+ON
+
+/* Med bilde */
+
+
+
+```
+
+## Standard ikon
+
+Sender du ikke inn `children` viser vi et standard brukerikon.
+
+
+
+## Størrelser
+
+`Avatar` kommer i flere størrelser. Tekst og ikon vil skalere, men det kan hende ikonet ditt må tilpasses.
+
+
+
+## Fargevarianter
+
+Du kan bruke alle tema fargene dine på `Avatar`.
+
+
+
+## Varianter
+
+Du kan endre mellom sirkel og firkantet form. Standard er sirkel.
+
+
+
+## Med bilde
+
+Skal du ha bilde, legger du dette som direkte barn av `Avatar`.
+
+Bilde (og andre child elements) får automatisk `aria-hidden="true"` for å unngå dobbel informasjon.
+
+
+
+## Komponering
+
+Du kan komponere `Avatar` inn i andre komponenter, som `DropdownMenu`, samt bruke komponenter som
+`Badge` rundt for å vise status.
+
+
+
+## Som lenke
+
+Legg lenker eller knapper rundt `Avatar` for å gjøre det klikkbart.
+Du må selv sette styling dersom du skal ha en interaktiv `Avatar`.
+
+
diff --git a/packages/react/src/components/Avatar/Avatar.stories.tsx b/packages/react/src/components/Avatar/Avatar.stories.tsx
new file mode 100644
index 0000000000..a6653f6cdb
--- /dev/null
+++ b/packages/react/src/components/Avatar/Avatar.stories.tsx
@@ -0,0 +1,129 @@
+import cat1 from '@assets/img/cats/Cat 3.jpg';
+import type { Meta, StoryFn } from '@storybook/react';
+
+import { BriefcaseIcon } from '@navikt/aksel-icons';
+import { Avatar } from '.';
+import { Badge, DropdownMenu } from '../';
+
+type Story = StoryFn;
+
+const meta: Meta = {
+ title: 'Komponenter/Avatar',
+ component: Avatar,
+ parameters: {
+ layout: 'padded',
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+export default meta;
+
+export const Preview: Story = (args) => ;
+
+Preview.args = {
+ 'aria-label': 'Ola Nordmann',
+ color: 'accent',
+ size: 'md',
+ variant: 'circle',
+ children: '',
+};
+
+export const NoName: Story = () => ;
+
+export const Sizes: Story = () => (
+ <>
+
+ xs
+
+
+
+ sm
+
+
+
+ md
+
+
+
+ lg
+
+
+ >
+);
+
+export const ColorVariants: Story = () => (
+ <>
+
+
+
+
+
+ >
+);
+
+export const ShapeVariants: Story = () => (
+ <>
+
+
+
+ ON
+
+
+ ON
+
+ >
+);
+
+export const WithImage: Story = () => (
+
+
+
+);
+
+export const InDropdownMenu: Story = () => (
+
+
+
+ ON
+
+ Velg Profil
+
+
+
+
+
+
+ ON
+
+
+ Ola Nordmann
+
+
+
+
+
+ Sogndal kommune
+
+
+
+
+);
+
+export const AsLink: Story = () => (
+
+
+
+);
diff --git a/packages/react/src/components/Avatar/Avatar.test.tsx b/packages/react/src/components/Avatar/Avatar.test.tsx
new file mode 100644
index 0000000000..3a52ac2d0c
--- /dev/null
+++ b/packages/react/src/components/Avatar/Avatar.test.tsx
@@ -0,0 +1,52 @@
+import { render, screen } from '@testing-library/react';
+import { Avatar } from './';
+
+describe('Avatar', () => {
+ it('should render correctly with default props', () => {
+ render();
+ expect(screen.getByRole('img')).toBeInTheDocument();
+ expect(screen.getByRole('img')).toHaveAttribute('data-ds-size', 'md');
+ expect(screen.getByRole('img')).toHaveAttribute(
+ 'data-ds-variant',
+ 'circle',
+ );
+ expect(screen.getByRole('img')).toHaveAttribute('data-ds-color', 'accent');
+ });
+
+ it('should render correctly with custom props', () => {
+ render();
+ expect(screen.getByRole('img')).toBeInTheDocument();
+ expect(screen.getByRole('img')).toHaveAttribute('data-ds-size', 'lg');
+ expect(screen.getByRole('img')).toHaveAttribute(
+ 'data-ds-variant',
+ 'square',
+ );
+ });
+
+ it('should render initials when aria-label is set', () => {
+ render(ON);
+ expect(screen.getByText('ON')).toBeInTheDocument();
+ });
+
+ it('should render children', () => {
+ render(
+
+
+ ,
+ );
+ /* look for image with correct id */
+ expect(screen.getByTestId('child-image')).toBeInTheDocument();
+ });
+
+ it('children should have aria-hidden', () => {
+ render(
+
+
+ ,
+ );
+ expect(screen.getByTestId('child-image')).toHaveAttribute(
+ 'aria-hidden',
+ 'true',
+ );
+ });
+});
diff --git a/packages/react/src/components/Avatar/Avatar.tsx b/packages/react/src/components/Avatar/Avatar.tsx
new file mode 100644
index 0000000000..6bba1129a3
--- /dev/null
+++ b/packages/react/src/components/Avatar/Avatar.tsx
@@ -0,0 +1,109 @@
+import { Slot } from '@radix-ui/react-slot';
+import cl from 'clsx/lite';
+import {
+ Fragment,
+ type HTMLAttributes,
+ type ReactNode,
+ forwardRef,
+} from 'react';
+
+export type AvatarProps = {
+ /**
+ * The name of the person the avatar represents.
+ */
+ 'aria-label': string;
+ /**
+ * The color of the avatar.
+ *
+ * @default 'accent'
+ */
+ color?: 'accent' | 'neutral' | 'brand1' | 'brand2' | 'brand3';
+ /**
+ * The size of the avatar.
+ *
+ * @default 'md'
+ */
+ size?: 'xs' | 'sm' | 'md' | 'lg';
+ /**
+ * The shape of the avatar.
+ *
+ * @default 'circle'
+ */
+ variant?: 'circle' | 'square';
+ /**
+ * Image, icon or initials to display inside the avatar.
+ *
+ * Gets `aria-hidden="true"`
+ */
+ children?: ReactNode;
+} & Omit, 'aria-label'>;
+
+const fontSizeMap = {
+ xs: 'ds-paragraph--xs',
+ sm: 'ds-heading--2xs',
+ md: 'ds-heading--sm',
+ lg: 'ds-heading--md',
+};
+
+/**
+ * Avatars are used to represent people or entities.
+ *
+ * @example
+ * JD
+ *
+ * @example
+ *
+ *
+ *
+ *
+ * @example
+ *
+ *
+ *
+ */
+export const Avatar = forwardRef(function Avatar(
+ {
+ 'aria-label': ariaLabel,
+ color = 'accent',
+ size = 'md',
+ variant = 'circle',
+ className,
+ children,
+ ...rest
+ },
+ ref,
+) {
+ const Component = children && typeof children !== 'string' ? Slot : Fragment;
+
+ return (
+
+
+ {children}
+
+
+ );
+});
+
+/**
+ * Gets initials using first and last word of a name.
+ */
+function getInitials(name: string | undefined): string | null {
+ // Leaving this function for perhaps later use
+ if (!name) return null;
+ const initials = [];
+ const segments = new Intl.Segmenter(document.documentElement.lang || 'no', {
+ granularity: 'word',
+ }).segment(name);
+ for (const segment of segments)
+ if (segment.isWordLike) initials.push(segment.segment);
+ return `${initials[0][0]}${initials.length > 1 ? initials[initials.length - 1][0] : ''}`;
+}
diff --git a/packages/react/src/components/Avatar/index.ts b/packages/react/src/components/Avatar/index.ts
new file mode 100644
index 0000000000..4a3dd72a47
--- /dev/null
+++ b/packages/react/src/components/Avatar/index.ts
@@ -0,0 +1,2 @@
+export { Avatar } from './Avatar';
+export type { AvatarProps } from './Avatar';
diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts
index f51a94c4ec..cf0d387c99 100644
--- a/packages/react/src/components/index.ts
+++ b/packages/react/src/components/index.ts
@@ -1,3 +1,4 @@
+export * from './Avatar';
export * from './Button';
export * from './Badge';
export * from './Breadcrumbs';