This guide is intended to help you get started with contributing to the project. By following these steps — which should take no more than 30 minutes —, you will understand the development process and workflow.
- Cloning the repository
- Installing Node.js and npm
- Installing dependencies
- Creating a new branch
- Starting the development server
- Creating a component
- Creating the default example
- Styling the example
- Writing the component documentation
- Writing another example
- Importing styles from other examples
- Writing documentation for other examples
- Submitting a pull request
This guide covers more advanced topics. Pick the topics based on your needs.
To start contributing to the project, you have to fork this repository and clone it to your local machine:
git clone https://github.com/YOUR_USERNAME/ariakit.git
If you are already part of the organization on GitHub, clone the repository directly:
git clone https://github.com/ariakit/ariakit.git
This repository uses npm workspaces to manage multiple projects. You need to install npm v7 or higher and Node.js v15 or higher.
You can run the following commands in your terminal to check your local Node.js and npm versions:
node -v
npm -v
If the versions are not correct or you don't have Node.js or npm installed, download them from https://nodejs.org.
Alternatively, you can use nvm to install the project's Node.js and npm versions. Once in the project's root directory, run the following command in your terminal:
nvm use
If you haven't installed the specific Node.js version yet,
nvm
will ask you to runnvm install
to install it. Follow the instructions in your terminal.
Once in the project's root directory, run the following command to install the project's dependencies:
npm install
Make sure you create a new branch for your changes. You can do this by running the following command in your terminal:
git checkout -b feat/my-component
After installing the project's dependencies, run the following command to start the development server:
npm run dev
On Windows, you should run this command as administrator or in developer mode. Otherwise, symlinks won't be created.
Now open http://localhost:3000 in your browser to see the project's site.
To make a new component, create a file with the following contents:
packages/ariakit/src/my-component/my-component.ts
import {
createComponent,
createElement,
createHook,
} from "ariakit-utils/system";
import { As, Options, Props } from "ariakit-utils/types";
/**
* Description for my component hook.
* @see https://ariakit.org/components/my-component
* @example
* ```jsx
* const props = useMyComponent();
* <Role {...props} />
* ```
*/
export const useMyComponent = createHook<MyComponentOptions>(
({ customProp = "My component", ...props }) => {
props = { children: customProp, ...props };
return props;
}
);
/**
* Description for my component.
* @see https://ariakit.org/components/my-component
* @example
* ```jsx
* <MyComponent />
* ```
*/
export const MyComponent = createComponent<MyComponentOptions>((props) => {
const htmlProps = useMyComponent(props);
return createElement("div", htmlProps);
});
export type MyComponentOptions<T extends As = "div"> = Options<T> & {
/**
* Description for custom prop.
*/
customProp?: string;
};
export type MyComponentProps<T extends As = "div"> = Props<
MyComponentOptions<T>
>;
That's the basic structure for all components in the project. This will guarantee that the component will support all the library's core features. You can take a look at other components to see more complex examples.
Finally, create an index.ts
file in the same directory as the component. This file will be used to export the component:
packages/ariakit/src/my-component/index.ts
export * from "./my-component";
The development workflow on this project is entirely based on examples. You can think of an example as a use case of a component. This will be used not only for development purposes, but also to show the component and its usage in the documentation.
Every component has a default example that receives the name of the component. This default example should be a common use case, but also simple enough so it requires as few custom props as possible.
Let's create a default example for our component:
packages/ariakit/src/my-component/__examples__/my-component/index.tsx
import { MyComponent } from "ariakit/my-component";
export default function Example() {
return <MyComponent />;
}
Now open http://localhost:3000/examples/my-component to see the example in action.
When necessary, you can apply styles to the example. We're using Tailwind to keep the styles consistent throughout the project. You will find the theme configuration in the tailwind.config.js
file.
To use Tailwind in a CSS file rather than applying classes directly to the HTML elements, we're using the
@apply
directive.Make sure you also take dark mode into account.
packages/ariakit/src/my-component/__examples__/my-component/style.css
.my-component {
@apply bg-danger-2 text-danger-2 dark:bg-danger-2-dark dark:text-danger-2-dark;
}
Now we need to import the CSS file on the example's index.tsx
file and add the class name to the respective elements:
packages/ariakit/src/my-component/__examples__/my-component/index.tsx
import { MyComponent } from "ariakit/my-component";
import "./style.css";
export default function Example() {
return <MyComponent className="my-component" />;
}
Now open http://localhost:3000/examples/my-component to see the example with the styles applied.
You'll notice that the transpiled CSS file has been also added to editor's files so people can easily edit it directly in the browser. You can also use it to see the output CSS while applying Tailwind classes.
One of the goals of having use cases written like that is so we can write automated tests for them. Instead of testing the Ariakit components directly, we're testing the examples that represent the way people use Ariakit components.
We use
ariakit-test
, which is a wrapper around React Testing Library with some additional features to ensure that events like clicks and key presses work similarly to actual user events.
Let's create a test for our example:
packages/ariakit/src/my-component/__examples__/my-component/test.tsx
import { render, getByText } from "ariakit-test";
import Example from ".";
test("my component", () => {
render(<Example />);
expect(getByText("My component")).toBeInTheDocument();
});
Now run the following command in your terminal to watch the test results:
npm run test-watch
Now we can write the documentation for the component itself using the example we just created.
We can create a readme.md
file in the component's directory and render an anchor element pointing to the example's index file with a data-playground
attribute. This will turn the link into a playground.
packages/ariakit/src/my-component/readme.md
# My component
This is my component.
<a href="./__examples__/my-component/index.tsx" data-playground>Example</a>
## Features
- Renders a nice "My component" text.
## Installation
```bash
npm install ariakit
```
Learn more on [Get started](/guide/get-started).
Now open http://localhost:3000/components/my-component to see the component documentation.
A component may have multiple examples besides the default one. This is useful when you want to show a component in different contexts and props.
Conventionally, the example names are prefixed with the component name and a short suffix. For example:
my-component-custom-prop
.
Let's create another example for our component:
packages/ariakit/src/my-component/__examples__/my-component-custom-prop/index.tsx
import { MyComponent } from "ariakit/my-component";
import "./style.css";
export default function Example() {
return <MyComponent className="my-component" customProp="Hello world" />;
}
Now open http://localhost:3000/examples/my-component-custom-prop to see the example with the custom prop applied.
We can @import
CSS files from other examples. You'll usually import the CSS file from the default example into the other examples so you don't need to repeat the same base styles.
packages/ariakit/src/my-component/__examples__/my-component-custom-prop/style.css
@import url("../my-component/style.css");
.my-component {
@apply p-4;
}
Unlike default examples, other examples will be primarily accessed through their own URLs (/examples/my-component-custom-prop
). To write documentation for them, we can create a readme.md
file in the example's directory and follow the same convention as for the component's readme.md
file.
packages/ariakit/src/my-component/__examples__/my-component-custom-prop/readme.md
# My component with `customProp`
This is my component using `customProp`.
<a href="./index.tsx" data-playground>Example</a>
Note that we're passing the `customProp` prop to the component:
```tsx
<MyComponent className="my-component" customProp="Hello world" />
```
When you're ready to submit a pull request, you can follow these naming conventions:
- Pull request titles use the Imperative Mood (e.g.,
Add something
,Fix something
). - Changesets use past tense verbs (e.g.,
Added something
,Fixed something
).
When you submit a pull request, GitHub will automatically lint, build, and test your changes. If you see an ❌, it's most likely a bug in your code. Please, inspect the logs through the GitHub UI to find the cause.
When adding new features or fixing bugs, we'll need to bump the package versions. We use Changesets to do this.
The action of adding a new example doesn't require a version bump. Only changes to the codebase that affect the public API or existing behavior (e.g., bugs) do.
Let's create a new changeset file for our component:
.changeset/my-component.md
---
"ariakit": minor
---
Added `MyComponent` component. ([#1271](https://github.com/ariakit/ariakit/pull/1271))
The name of the file doesn't really matter as long as it's unique across changesets. Just try to name it in a way that we can easily remember what the change was when reviewing the all the changesets.
Once your pull request is merged into the main
branch, the Publish
PR will be automatically created/updated with the new changes. Once we merge this PR, the affected packages will be automatically published to npm and the changelog will be updated.
Ariakit supports both React 17 and React 18. If you want to see if your example works with React 17, you can run the following commands.
Development server:
npm run dev-react-17
Tests:
npm run test-react17
These commands will automatically re-install React 18 at the end of the process (e.g., when you stop the development server). If, for some reason, this doesn't happen automatically, you should run npm i
in your terminal.
Most of the time, we'll write unit and integration tests as described on Testing the example. Those tests simulate real user interactions, but they don't run in the browser. They use JSDOM, which implements JavaScript DOM APIs in a Node.js environment.
Combined with the ariakit-test
package, this is more than enough for 99% of the cases. However, sometimes we need a real browser to test specific interactions with our examples that aren't supported in JSDOM. For those cases, we use Playwright.
Let's create an end-to-end test for our example:
packages/ariakit/src/my-component/__examples__/my-component/test-chrome.ts
import { expect, test } from "@playwright/test";
test("my component", async ({ page }) => {
await page.goto("/examples/my-component");
const element = await page.locator("text=My component");
await expect(eleemnt).toBeVisible();
});
Now run the following command in your terminal to see the test results (make sure to replace my-component
with the name of your example, or omit it to run all the tests):
Note: The development server must be running in another terminal instance.
npm run test-chrome my-component
You can also run the tests in headed mode:
npm run test-chrome-browser my-component
Or in debug mode:
npm run test-chrome-debug my-component