Contributing

We're thrilled you want to contribute - this guide explains how our UI library works and how to get started.

Setup the Project

Start by cloning the project to your local machine by running the following command in your terminal:

git clone git@github.com:nettbureau/vibrant-ui-next.git

With the correct authentication tokens in your global .npmrc file (see Installation), install dependencies, then run the dev script from the root package.json to start the dev server in watch mode. You're ready to go.

Project Structure

Our UI library uses a monorepo, keeping all packages and apps in one place. This makes code sharing, refactoring, and maintaining consistency easier—perfect for a project that depends on reuse and alignment across components.

All packages are located in the packages folder, and all applications are located in the apps folder.

Technologies

While the Introduction section gives a brief overview of the technologies we use in the project itself, this overview will go into more detail about the technologies we use to build and develop the project:

  • Git - For version control and collaboration.
  • Bun - A package manager for installing dependencies and running scripts.
  • Turborepo - A monorepo tool that enables us to manage multiple packages and applications in a single repository.
  • Storybook - A tool to test and document components in isolation.
  • Vite - A tool to bundle JavaScript and TypeScript files.
  • Vitest - A Jest-compatible test runner running on top of Vite.
  • ESLint - A tool to lint JavaScript and TypeScript files.
  • Prettier - A tool to format JavaScript and TypeScript files.

Commands

It's good to know which commands are available to you when working on the project. Here's a list of all commands:

  • bun build - This builds all apps and packages in the project.
  • bun clean - This removes all build and cache artifacts from the project.
  • bun clean:lock - This removes the lock file and cleans the project.
  • bun dev - This command starts a development server in watch mode.
  • bun format - This formats all files in the project using Prettier.
  • bun lint - This lints all files in the project using ESLint.
  • bun lint:fix - This lints all files in the project using ESLint and fixes any fixable issues.
  • bun test - This runs all tests in the project.
  • bun test:coverage - This runs all tests in the project and generates a coverage report.
  • bun test:watch - This runs all tests in the project and watches for changes.

Most of these commands are also available in each individual package.

Workflows

The project contains two Github workflows: ci.yml and cd.yml.

Continuous Integration

The ci.yml workflow will run on pull request creation and updates, and runs the following jobs:

  • Verify - Runs build, linting and tests to verify that the code is compliant to coding standards.
  • Annotate PR - Automatically assigns the author as the assignee and adds labels based on conventional commits.
  • Report - Posts a comment with the size changes of the code in the pull request.

Continuous Deployment

The cd.yml workflow will run on pushes to the main branch, and runs the following jobs:

  • Release - Releases packages to the Github Package Registry.

Editor Configuration

The project contains a .vscode folder with some recommended extensions and settings for Visual Studio Code. If you're using another editor, make sure it has support for EditorConfig, ESLint and Prettier.

Feel free to add your own editor-specific configuration to the project, as long as it's not computer-specific.

Git Standards

We follow a few conventions to keep the history clean and consistent.

Global Ignore File

Project .gitignore should contain only project-specific entries. For machine-specific files (e.g., .DS_Store, `.idea``), create a global ignore file:

touch ~/.gitignore

Then configure Git to use it:

git config --global core.excludesfile ~/.gitignore

It's your responsibility to make sure that unrelated files are not committed to the project.

Branches

Always create a new branch from the latest main before starting work:

git checkout main
git pull
git checkout -b feat/my-feature

Branch names use type/subject in kebab-case, for example feat/calendar or fix/button-label.

Common types: feat, fix, docs, refactor, test, style, chore, revert.

Commits

We use Conventional Commits with the format:

type(scope): subject
  • scope should be the package, app, or component name. For @vibrant-ui/components, use the component itself (e.g., button).
  • Use present tense and imperative mood (e.g., add color option, not added color option).
  • Keep the subject short; add details in the body after a blank line if needed.
  • Commit often to track progress and make collaboration easier.

In an ideal world, each commit should be buildable and testable. This is an ideal to strive for, but not always possible.

Pull Requests

Pull requests will automatically be tagged with labels based on the conventional commit messages in the pull request. It will also auto-assign the author as the assignee. However, the title should be descriptive and should be in regular sentence case with a capital letter at the beginning.

Try to always get a review from at least one fellow developer before merging a pull request.

Rebasing

If your pull request is behind the main branch, you need to catch up. Either by merging the main branch into your branch, or by rebasing your branch on top of the main branch. Rebasing is preferred because it keeps the commit history clean and linear. To rebase your branch, run the following commands:

git checkout main
git pull
git checkout INSER_BRANCH_NAME
git rebase main

Then after resolving any conflicts, run git rebase --continue to continue the rebase. If you want to abort the rebase, run git rebase --abort. This will reset your branch to the state it was in before you started rebasing. When you're done rebasing, you need to force push your branch to update the pull request.

git push --force-with-lease

Squashing Commits

Depending on the size of the pull request, you may want to squash your commits before merging to make the changes easier to understand. The important thing is to leave a tail of commits that are easy to understand and that can be used to generate a changelog. This can be done using the following commands:

git rebase -i HEAD~N

Where N is the number of commits you want to squash. This will open an editor with a list of the last N commits. You can then change the pick command to squash or fixup for all commits except the first one. This will squash all commits into the first one. You can then edit the commit message to make it more descriptive and save your changes. This will squash all commits into the first one. Then you can force push your branch to update the pull request:

git push --force-with-lease

Semantic Versioning

We use Semantic Versioning to version our packages. This means that we use the MAJOR.MINOR.PATCH format to version our packages, for example 1.0.0. Here's a list of the different types of changes that can be made to a package:

  • Patch - A patch change is a change that fixes a bug in a backwards-compatible manner. A commit message with the fix type will automatically trigger a patch version bump.
  • Minor - A minor change is a change that adds functionality in a backwards-compatible manner. A commit message with the feat type will automatically trigger a minor version bump.
  • Major - A major change is a change that breaks backwards compatibility. To trigger a major version bump, you can add an exclamation mark after the type, like this: feat(toggle)!: rename switch to toggle.

Coding Standards

We use ESLint to lint our code, and Prettier to format our code. Most of our coding standards are enforced by these two, but there are a few things you're in control of. Here's a list of things you should keep in mind when writing code:

Naming conventions

Naming functions and variables is an important aspect of writing clear and maintainable code. Good names help make code more readable and understandable to both the original author and other developers who may work with the code later on.

Here are some tips for choosing good names for functions and variables:

  • Be descriptive - The name of the function or variable should describe what it does or represents as clearly and concisely as possible. Avoid vague or ambiguous names that don't convey the purpose of the code.

  • Use full words - Avoid abbreviations, acronyms, or shortened words that may not be clear to other developers. While shorter names can save time typing, it can make the code more difficult to understand and maintain in the long run.

  • Consistency - Use the same name for the same thing throughout your code. This helps avoid confusion when working with different parts of the code and makes it easier to search for specific functions and variables.

  • Use meaningful names - Use nouns in variable names to indicate what they represent and try to use verbs in function names to indicate what action is being performed.

If you're still having problems coming up with a good name, consider if the code can be refactored to make it easier to understand.

Blank lines

To decide which lines of code to group together and which to separate with empty lines, consider the logical structure of your program. Group lines that are related in function or purpose, such as those that perform a specific task or operate on a particular set of data. Use empty lines to separate different sections of the code, such as functions, imports, or conditional blocks.

As a general rule, try to always add a blank line before the last return statement of a function.

Comments

Comments are an essential tool to convey how the code works, and we encourage you to use them extensively in your project. However, it's important to avoid comments that merely restate what is already clear from the code. To make your comments more effective, strive to write them in active voice, where the subject of a sentence performs the action. This style of writing can make your comments clearer and more concise. A comment should convey the intent of the code, not just the code itself.

Comments have a tendency to become outdated as the code changes. To avoid this, try to keep your comments as close to the code they describe as possible. Care for your comments as you would for your code, and update them when necessary.

Docblocks

This project uses JSDoc to generate documentation from docblock comments. This means that you should always use JSDoc comments for exported functions and variables. Some of these comments might seem redundant, but are still valuable for documentation reasons. Below is an example of a well written JSDoc comment:

/**
 * Calculates and returns the area of a rectangle based on the provided width and height. This can
 * be useful for various layout calculations.
 * @param width - The width of the rectangle.
 * @param height - The height of the rectangle.
 * @returns The area of the rectangle.
 */
export function calculateRectangleArea(width: number, height: number): number {
  return width * height;
}

Note how the width of the comment never exceeds 100 characters. This makes it easier to read the comment in an editor. This is unfortunately not enforced by ESLint, but it's a good practice to follow, even though it requires some manual effort.

Artificial Intelligence

To further enhance your comments, consider using AI tools like Github Copilot or ChatGPT to help you write better and more meaningful comments. These tools can assist you in generating concise and descriptive comments and guide you on how to use active voice effectively.

Exports

When exporting a function or variable, try to use named exports instead of default exports. This makes it easier to understand what is being exported, and also makes it easier to refactor code later on. Check out this article for more information.

Alphabetize Lists

When having a list of items and there's no natural order, try to fall back to alphabetical order. This makes it easier to find items in the list as it grows.

Troubleshooting

If you encounter any problems while working with our library, check the list below for common issues and solutions. If you can't find a solution here or through a web search, please reach out to your fellow developers for assistance. If you've encountered an issue and have found a solution, please consider adding a section to the list below. This will help other developers in the future.

I can't install dependencies

Ensure your .npmrc file is configured correctly. Check the Installation section for guidance. If the build script is failing, refer to the note on build errors below. As a last resort, try removing the lockfile and reinstalling dependencies.

My build is failing

If the build is failing even though there are no errors in your code, there may be a problem with caching across packages. Try running clean:lock in root.