diff --git a/.husky/pre-commit b/.husky/pre-commit
old mode 100644
new mode 100755
diff --git a/src/components/Accordion/Accordion.module.css b/src/components/Accordion/Accordion.module.css
new file mode 100644
index 00000000..2a10a2e8
--- /dev/null
+++ b/src/components/Accordion/Accordion.module.css
@@ -0,0 +1,6 @@
+.accordion {
+ --component-accordion-color-background: var(--colors-neutral-000);
+ --component-panel-size-width: 100%;
+ background-color: var(--component-accordion-color-background);
+ width: var(--component-panel-size-width);
+}
diff --git a/src/components/Accordion/Accordion.stories.module.css b/src/components/Accordion/Accordion.stories.module.css
new file mode 100644
index 00000000..bfd85744
--- /dev/null
+++ b/src/components/Accordion/Accordion.stories.module.css
@@ -0,0 +1,3 @@
+.container {
+ width: 80vw;
+}
diff --git a/src/components/Accordion/Accordion.stories.tsx b/src/components/Accordion/Accordion.stories.tsx
new file mode 100644
index 00000000..77c77ef6
--- /dev/null
+++ b/src/components/Accordion/Accordion.stories.tsx
@@ -0,0 +1,87 @@
+import React, { useState } from 'react';
+import type { ComponentStory, ComponentMeta } from '@storybook/react';
+import { config } from 'storybook-addon-designs';
+import cn from 'classnames';
+
+import { StoryPage } from '@sb/StoryPage';
+
+import { Button } from '../Button';
+
+import { Accordion } from './Accordion';
+import { AccordionHeader } from './AccordionHeader';
+import { AccordionContent } from './AccordionContent';
+import classes from './Accordion.stories.module.css';
+
+const figmaLink = ''; // TODO: Add figma link
+
+export default {
+ title: `Components/Accordion`,
+ component: Accordion,
+ parameters: {
+ design: config([
+ {
+ type: 'figma',
+ url: figmaLink,
+ },
+ {
+ type: 'link',
+ url: figmaLink,
+ },
+ ]),
+ docs: {
+ page: () => (
+
+ ),
+ },
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = () => {
+ const [open1, setOpen1] = useState(false);
+ const [open2, setOpen2] = useState(false);
+
+ const handleClick1 = () => {
+ setOpen1(!open1);
+ };
+
+ const handleClick2 = () => {
+ setOpen2(!open2);
+ };
+
+ const AccordionExampleContent =
+ 'Accordion-innhold uten css for å tilrettelegge for selvalgt styling';
+
+ const ActionButton = ;
+ return (
+
+
+ Accordion 1
+ {AccordionExampleContent}
+
+
+ Accordion 2
+ {AccordionExampleContent}
+
+
+ );
+};
+
+export const Example = Template.bind({});
+Example.args = {
+ // TODO: Add story specific args
+};
+Example.parameters = {
+ docs: {
+ description: {
+ story: '', // TODO: add story description, supports markdown
+ },
+ },
+};
diff --git a/src/components/Accordion/Accordion.test.tsx b/src/components/Accordion/Accordion.test.tsx
new file mode 100644
index 00000000..a15f9c18
--- /dev/null
+++ b/src/components/Accordion/Accordion.test.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import userEvent from '@testing-library/user-event';
+import { render as renderRtl, screen } from '@testing-library/react';
+
+import type { AccordionProps } from './Accordion';
+import { Accordion } from './Accordion';
+import { AccordionHeader } from './AccordionHeader';
+import { AccordionContent } from './AccordionContent';
+
+const render = (props: Partial = {}) => {
+ const allProps = {
+ children: (
+ <>
+ AccordionHeader
+ AccordionContent
+ >
+ ),
+ onClick: jest.fn(),
+ open: true,
+ ...props,
+ };
+ renderRtl();
+};
+
+const user = userEvent.setup();
+
+describe('Accordion', () => {
+ it('should call handleClick when AccordionHeader is clicked', async () => {
+ const handleClick = jest.fn();
+ render({ onClick: handleClick });
+
+ await user.click(screen.getByRole('button', { name: 'AccordionHeader' }));
+ expect(handleClick).toHaveBeenCalledTimes(1);
+ });
+
+ it('should have aria-expanded=true when open=true', () => {
+ render({ open: true });
+ expect(
+ screen.getByRole('button', { name: 'AccordionHeader', expanded: true }),
+ ).toBeInTheDocument();
+ });
+
+ it('should have aria-expanded=false when open=false', () => {
+ render({ open: false });
+
+ expect(
+ screen.getByRole('button', { name: 'AccordionHeader', expanded: false }),
+ ).toBeInTheDocument();
+ });
+
+ it('should call handleClick when AccordionHeader is clicked using key press Space', async () => {
+ const handleClick = jest.fn();
+ render({ onClick: handleClick });
+
+ const accordionHeader = screen.getByRole('button', {
+ name: 'AccordionHeader',
+ });
+ await user.type(accordionHeader, '{Space}');
+ expect(handleClick).toHaveBeenCalledTimes(1);
+ });
+
+ it('should call handleClick when AccordionHeader is clicked using key press Enter', async () => {
+ const handleClick = jest.fn();
+ render({ onClick: handleClick });
+
+ await user.keyboard('{Tab}');
+ await user.keyboard('{Enter}');
+ expect(handleClick).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/components/Accordion/Accordion.tsx b/src/components/Accordion/Accordion.tsx
new file mode 100644
index 00000000..32d4d616
--- /dev/null
+++ b/src/components/Accordion/Accordion.tsx
@@ -0,0 +1,32 @@
+import React, { useId } from 'react';
+
+import type { ClickHandler } from './Context';
+import { AccordionContext } from './Context';
+import classes from './Accordion.module.css';
+
+export interface AccordionProps {
+ children?: React.ReactNode;
+ onClick: ClickHandler;
+ open: boolean;
+}
+
+export const Accordion = ({ children, open, onClick }: AccordionProps) => {
+ const headerId = useId();
+ const contentId = useId();
+ return (
+
+ );
+};
+
+export default Accordion;
diff --git a/src/components/Accordion/AccordionContent.tsx b/src/components/Accordion/AccordionContent.tsx
new file mode 100644
index 00000000..7726f601
--- /dev/null
+++ b/src/components/Accordion/AccordionContent.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+
+import { useAccordionContext } from './Context';
+
+export interface AccordionContentProps {
+ children?: React.ReactNode;
+}
+
+export const AccordionContent = ({ children }: AccordionContentProps) => {
+ const { open, contentId, headerId } = useAccordionContext();
+
+ return (
+
+ {open && (
+
+ {children}
+
+ )}
+
+ );
+};
+
+export default AccordionContent;
diff --git a/src/components/Accordion/AccordionHeader.module.css b/src/components/Accordion/AccordionHeader.module.css
new file mode 100644
index 00000000..4797370f
--- /dev/null
+++ b/src/components/Accordion/AccordionHeader.module.css
@@ -0,0 +1,55 @@
+.accordion-header {
+ --component-accordion_header-border_top_style: solid;
+ --component-accordion_header-border_top_color: var(--colors-neutral-200);
+ --component-accordion_header-border_top_width: var(--border-width-thin);
+ --component-accordion_header-color-background: var(--colors-neutral-000);
+ display: flex;
+ border-top-width: var(--component-accordion_header-border_top_width);
+ border-top-style: var(--component-accordion_header-border_top_style);
+ border-top-color: var(--component-accordion_header-border_top_color);
+ background-color: var(--component-accordion_header-color-background);
+}
+
+.accordion-header__title {
+ --component-accordion_header_title-spacing-margin_left: 2.5rem;
+ --component-accordion_header_title-border_top_style: none;
+ --component-accordion_header_title-border_bottom_style: none;
+ --component-accordion_header_title-border_right_style: none;
+ --component-accordion_header_title-border_left_style: none;
+ --component-accordion_header_title-font_size: var(--font-size-300);
+ --component-accordion_header_title-font_weight: var(
+ --component-panel-weight-heading
+ );
+ --component-accordion_header_title-color-background: none;
+ font-family: inherit;
+ flex: 1 1 auto;
+ border-top-style: var(--component-accordion_header_title-border_top_style);
+ border-bottom-style: var(
+ --component-accordion_header_title-border_bottom_style
+ );
+ border-left-style: var(--component-accordion_header_title-border_left_style);
+ border-right-style: var(
+ --component-accordion_header_title-border_right_style
+ );
+ background-color: var(--component-accordion_header_title-color-background);
+ text-align: var(--component-accordion_header_title-text-align);
+ margin-left: var(--component-accordion_header_title-margin-left);
+ font-size: var(--component-accordion_header_title-font_size);
+ font-weight: var(--component-accordion_header_title-font_weight);
+ margin-left: var(--component-accordion_header_title-spacing-margin_left);
+}
+
+.accordion-header__actions {
+ margin-top: 0.3rem;
+}
+
+.accordion-header__icon {
+ padding-top: 1rem;
+ margin-left: 2.5rem;
+}
+
+.accordion-header__icon--opened {
+ transform: rotate(90deg);
+ margin-top: 0.3rem;
+ margin-left: 3rem;
+}
diff --git a/src/components/Accordion/AccordionHeader.tsx b/src/components/Accordion/AccordionHeader.tsx
new file mode 100644
index 00000000..818b46b4
--- /dev/null
+++ b/src/components/Accordion/AccordionHeader.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import cn from 'classnames';
+
+import classes from './AccordionHeader.module.css';
+import { useAccordionContext } from './Context';
+import { ReactComponent as ExpandCollapseArrow } from './expand-collapse.svg';
+
+export interface AccordionHeaderProps {
+ children?: React.ReactNode;
+ actions?: React.ReactNode;
+}
+
+export const AccordionHeader = ({
+ children,
+ actions,
+}: AccordionHeaderProps) => {
+ const { onClick, open, headerId, contentId } = useAccordionContext();
+
+ return (
+
+ );
+};
+
+export default AccordionHeader;
diff --git a/src/components/Accordion/Context.ts b/src/components/Accordion/Context.ts
new file mode 100644
index 00000000..a110bd4a
--- /dev/null
+++ b/src/components/Accordion/Context.ts
@@ -0,0 +1,24 @@
+import { createContext, useContext } from 'react';
+
+export type ClickHandler = () => void;
+
+export const AccordionContext = createContext<
+ | {
+ open: boolean;
+ onClick: ClickHandler;
+ headerId: string;
+ contentId: string;
+ }
+ | undefined
+>(undefined);
+
+export const useAccordionContext = () => {
+ const context = useContext(AccordionContext);
+ if (context === undefined) {
+ throw new Error(
+ 'useAccordionContext must be used within a AccordionContext',
+ );
+ }
+
+ return context;
+};
diff --git a/src/components/Accordion/expand-collapse.svg b/src/components/Accordion/expand-collapse.svg
new file mode 100644
index 00000000..58625dc5
--- /dev/null
+++ b/src/components/Accordion/expand-collapse.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/Accordion/index.ts b/src/components/Accordion/index.ts
new file mode 100644
index 00000000..d31f202e
--- /dev/null
+++ b/src/components/Accordion/index.ts
@@ -0,0 +1,3 @@
+export { Accordion } from './Accordion';
+export { AccordionHeader } from './AccordionHeader';
+export { AccordionContent } from './AccordionContent';
diff --git a/src/components/index.ts b/src/components/index.ts
index 0d7ed298..b27a060e 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -2,4 +2,5 @@ export { Panel, PanelVariant } from './Panel';
export { CircularProgress } from './CircularProgress';
export { AppWrapper } from './AppWrapper';
export { ToggleButton, ToggleButtonGroup } from './ToggleButtonGroup';
+export { Accordion, AccordionHeader, AccordionContent } from './Accordion';
export { Button, ButtonVariant } from './Button';