diff --git a/packages/react/.eslintignore b/packages/react/.eslintignore
new file mode 100644
index 0000000..76add87
--- /dev/null
+++ b/packages/react/.eslintignore
@@ -0,0 +1,2 @@
+node_modules
+dist
\ No newline at end of file
diff --git a/packages/react/.eslintrc.json b/packages/react/.eslintrc.json
new file mode 100644
index 0000000..a1273cc
--- /dev/null
+++ b/packages/react/.eslintrc.json
@@ -0,0 +1,15 @@
+{
+ "root": true,
+ "extends": [
+ "custom"
+ ],
+ "parserOptions": {
+ "project": [
+ "./tsconfig.json",
+ "./tsconfig.node.json"
+ ]
+ },
+ "rules": {
+ "import/no-extraneous-dependencies": ["error", {"devDependencies": ["vite.config.ts", "**/*.stories.tsx", "**/*.spec.tsx"]}]
+ }
+}
\ No newline at end of file
diff --git a/packages/react/package.json b/packages/react/package.json
new file mode 100644
index 0000000..a817fd1
--- /dev/null
+++ b/packages/react/package.json
@@ -0,0 +1,68 @@
+{
+ "name": "@openfun/cunningham-react",
+ "private": false,
+ "version": "0.0.0",
+ "type": "module",
+ "license": "MIT",
+ "module": "./dist/index.js",
+ "main": "./dist/index.cjs",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs",
+ "types": "./dist/index.d.ts"
+ },
+ "./style": "./dist/style.css"
+ },
+ "files": [
+ "dist/"
+ ],
+ "scripts": {
+ "lint": "eslint . 'src/**/*.{ts,tsx}'",
+ "dev": "yarn storybook & nodemon --watch src --ext '*' --exec npm run build",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "test": "FORCE_COLOR=1 vitest run",
+ "test-watch": "vitest",
+ "coverage": "vitest run --coverage",
+ "storybook": "start-storybook -p 6006"
+ },
+ "dependencies": {
+ "@fontsource/roboto": "4.5.8",
+ "@openfun/cunningham-tokens": "*",
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "devDependencies": {
+ "@babel/core": "7.20.5",
+ "@openfun/typescript-configs": "*",
+ "@storybook/addon-actions": "6.5.13",
+ "@storybook/addon-essentials": "6.5.13",
+ "@storybook/addon-interactions": "6.5.13",
+ "@storybook/addon-links": "6.5.13",
+ "@storybook/builder-vite": "0.2.5",
+ "@storybook/preset-scss": "1.0.3",
+ "@storybook/react": "6.5.13",
+ "@storybook/testing-library": "0.0.13",
+ "@testing-library/dom": "8.19.0",
+ "@testing-library/react": "13.4.0",
+ "@types/react": "18.0.25",
+ "@types/react-dom": "18.0.9",
+ "@vitejs/plugin-react": "2.2.0",
+ "@vitest/ui": "0.25.3",
+ "babel-loader": "8.3.0",
+ "css-loader": "6.7.2",
+ "jsdom": "20.0.3",
+ "sass": "1.56.1",
+ "sass-loader": "13.2.0",
+ "style-loader": "3.3.1",
+ "typescript": "4.9.3",
+ "vite": "3.2.4",
+ "vite-plugin-dts": "1.7.1",
+ "vitest": "0.25.3"
+ }
+}
diff --git a/packages/react/public/vite.svg b/packages/react/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/packages/react/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/react/src/components/Button/index.scss b/packages/react/src/components/Button/index.scss
new file mode 100644
index 0000000..6ed37ea
--- /dev/null
+++ b/packages/react/src/components/Button/index.scss
@@ -0,0 +1,8 @@
+.c__button {
+ background-color: var(--c--colors--primary);
+ padding: 8px 30px;
+ border-radius: 6px;
+ border: none;
+ color: white;
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/packages/react/src/components/Button/index.spec.tsx b/packages/react/src/components/Button/index.spec.tsx
new file mode 100644
index 0000000..4f3c68a
--- /dev/null
+++ b/packages/react/src/components/Button/index.spec.tsx
@@ -0,0 +1,12 @@
+import { describe, expect, it } from "vitest";
+import { render, screen } from "@testing-library/react";
+import React from "react";
+import { Button } from "./index";
+
+describe("", () => {
+ it("renders", () => {
+ render();
+ const button = screen.getByRole("button", { name: "" });
+ expect(button.classList.contains("c__button")).toBe(true);
+ });
+});
diff --git a/packages/react/src/components/Button/index.stories.tsx b/packages/react/src/components/Button/index.stories.tsx
new file mode 100644
index 0000000..3a59f00
--- /dev/null
+++ b/packages/react/src/components/Button/index.stories.tsx
@@ -0,0 +1,13 @@
+import { ComponentMeta, ComponentStory } from "@storybook/react";
+import React from "react";
+import { Button } from "./index";
+
+export default {
+ title: "Button",
+ component: Button,
+} as ComponentMeta;
+
+const Template: ComponentStory = () => ;
+
+export const Default = Template.bind({});
+Default.args = {};
diff --git a/packages/react/src/components/Button/index.tsx b/packages/react/src/components/Button/index.tsx
new file mode 100644
index 0000000..52b646a
--- /dev/null
+++ b/packages/react/src/components/Button/index.tsx
@@ -0,0 +1,5 @@
+import React from "react";
+
+export const Button = () => {
+ return ;
+};
diff --git a/packages/react/src/index.scss b/packages/react/src/index.scss
new file mode 100644
index 0000000..83b7b4c
--- /dev/null
+++ b/packages/react/src/index.scss
@@ -0,0 +1,6 @@
+@import "@fontsource/roboto/300";
+@import "@fontsource/roboto/400";
+@import "@fontsource/roboto/700";
+@import "@fontsource/roboto/900";
+@import '@openfun/cunningham-tokens/default-tokens';
+@import './components/Button';
\ No newline at end of file
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
new file mode 100644
index 0000000..126f5b2
--- /dev/null
+++ b/packages/react/src/index.ts
@@ -0,0 +1,3 @@
+import "./index.scss";
+
+export * from "./components/Button";
diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json
new file mode 100644
index 0000000..88c9b8c
--- /dev/null
+++ b/packages/react/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "@openfun/typescript-configs/react.json",
+ "compilerOptions": {
+ "noEmit": true,
+ },
+ "include": ["src"],
+ "exclude": ["node_modules","dist"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/packages/react/tsconfig.node.json b/packages/react/tsconfig.node.json
new file mode 100644
index 0000000..9d31e2a
--- /dev/null
+++ b/packages/react/tsconfig.node.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/packages/react/vite.config.ts b/packages/react/vite.config.ts
new file mode 100644
index 0000000..fd0b02b
--- /dev/null
+++ b/packages/react/vite.config.ts
@@ -0,0 +1,32 @@
+import { defineConfig } from "vitest/config";
+import react from "@vitejs/plugin-react";
+import dts from "vite-plugin-dts";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ build: {
+ emptyOutDir: true,
+ outDir: "dist",
+ sourcemap: true,
+ lib: {
+ entry: {
+ index: "./src/index.ts",
+ },
+ formats: ["es"],
+ },
+ rollupOptions: {
+ external: ["react", "react-dom"],
+ output: {
+ globals: {
+ react: "React",
+ "react-dom": "ReactDOM",
+ },
+ },
+ },
+ },
+ plugins: [dts(), react()],
+ test: {
+ environment: "jsdom",
+ globals: true,
+ },
+});