Frontfriend

Contributions

How to safely customize Frontfriend components in your project and contribute improvements back to the community

Contributions

Frontfriend components are designed to be owned by you. Once you download a component into your project, the source code lives in your repo and you are free to change it however you want. This page covers the two scenarios you will run into:

  1. Customizing a component for your team only — keep your changes private without breaking future updates.
  2. Contributing an improvement back — share a useful change with everyone via the public frontfriend-contributions repository.

1. Customizing Components in Your Project

When you run npx frontfriend download <component>, the component is written into your project (typically components/ui/<name>/). From that moment, the file is yours — but there is a catch.

Do not edit the original component file directly. Running npx frontfriend download again (or updating components in bulk) will overwrite your changes. Use the duplicate-and-rename pattern below instead.

The Duplicate-and-Rename Pattern

The safe way to customize a Frontfriend component is to duplicate it, rename it with a proprietary prefix, and edit the copy. The original component stays untouched and continues to receive updates from Frontfriend.

Step 1 — Pick a prefix for your team

Use a short, consistent prefix derived from your company or product name. Two letters is plenty.

CompanyPrefixExample component
Acme CorpAcAcInput
GlobexGxGxButton
Stark IndustriesSiSiCard

Stick to the same prefix across the whole project so any developer can recognize a customized component at a glance.

Step 2 — Duplicate the component

Take the original component and copy it under a new name:

# React
cp -r components/ui/input components/ui/ac-input
 
# Vue
cp -r components/ui/input components/ui/ac-input

Step 3 — Rename the exported component

Rename the component itself (and its file) to use your prefix:

// components/ui/ac-input/AcInput.tsx
import * as React from "react";
import { cn } from "@frontfriend/tailwind/lib/react/utils";
 
export interface AcInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  // your custom props
}
 
const AcInput = React.forwardRef<HTMLInputElement, AcInputProps>(
  ({ className, ...props }, ref) => {
    return (
      <input
        ref={ref}
        className={cn("bg-layer-control border border-neutral-subtle ...", className)}
        {...props}
      />
    );
  },
);
AcInput.displayName = "AcInput";
 
export { AcInput };

Step 4 — Update the barrel export

// components/ui/ac-input/index.ts
export { AcInput } from "./AcInput";

Step 5 — Use it like any other component

import { AcInput } from "@/components/ui/ac-input";
 
<AcInput placeholder="Custom input for Acme..." />

Why This Works

  • Frontfriend's CLI looks at the registry name (e.g. input) to decide what to overwrite. Your ac-input directory is invisible to the CLI, so it is never touched.
  • You can still pull updates for the original Input component without conflicts.
  • Any developer reading the code immediately knows that anything starting with your prefix is owned by your team, not by Frontfriend.

When to Customize Locally

Use this pattern when the change is:

  • Specific to your product — e.g. a domain-specific input that masks a tax code.
  • A one-off variant that does not generalize to other Frontfriend users.
  • Experimental — you want to try something internally before proposing it.

If the change is generic and useful for everyone, jump to the next section instead.


2. Contributing Back to Frontfriend

When you build something that other Frontfriend teams could benefit from, you can contribute it back through the public frontfriend-contributions repository.

This is the official channel for proposing:

  • New components that do not yet exist in the registry.
  • Enhancements to existing components (new variants, accessibility fixes, bug fixes, performance improvements).

Contributions are reviewed by the Frontfriend team. Approved contributions are merged into the official registry and distributed to all users via npx frontfriend download.

Repository Structure

The contribution repo is organized as a single, flat contributions folder — no separate "proposal" vs "enhancement" trees, just a meta.json tag that tells the reviewers what kind of change you are submitting.

frontfriend-contributions/
├── README.md                     # Overview and philosophy
├── CONTRIBUTING.md               # Step-by-step PR process
├── contributions/
│   ├── react/
│   │   └── <name>/
│   │       ├── meta.json         # type, target, author, dependencies
│   │       ├── <Component>.tsx
│   │       ├── index.ts
│   │       ├── README.md         # what it does, why it should land
│   │       └── examples/         # usage examples (optional)
│   └── vue/
│       └── <name>/
│           ├── meta.json
│           ├── <Component>.vue
│           ├── index.ts
│           ├── README.md
│           └── examples/
└── .github/
    ├── ISSUE_TEMPLATE/
    │   ├── component-proposal.md
    │   └── enhancement.md
    └── PULL_REQUEST_TEMPLATE.md

The meta.json Tag

Every contribution carries a meta.json that tells the reviewers what kind of change it is. The type field is the key signal:

{
  "type": "new",
  "name": "stepper",
  "framework": "react",
  "author": "@your-github-handle",
  "summary": "A multi-step form indicator with horizontal and vertical layouts.",
  "dependencies": ["lucide-react"],
  "registryDependencies": ["icon"]
}

For an enhancement, point at the component you are modifying:

{
  "type": "enhancement",
  "target": "button",
  "framework": "react",
  "author": "@your-github-handle",
  "summary": "Add a `loadingText` prop that announces loading state to screen readers."
}
FieldRequiredDescription
typeyes"new" for brand-new components, "enhancement" for changes to existing ones
namenew onlyComponent name in kebab-case (e.g. stepper, command-palette)
targetenhancement onlyThe existing component being modified (e.g. button)
frameworkyes"react" or "vue"
authoryesYour GitHub handle
summaryyesOne-sentence description of the change
dependenciesoptionalnpm packages required by the component
registryDependenciesoptionalOther Frontfriend components this one depends on

Drop the prefix when you contribute. The Ac/Gx/Si prefix is only for your private fork. When the same component goes into the public registry, use the canonical, unprefixed name (e.g. Stepper, not AcStepper) so it works for every team.

How to Open a Contribution PR

  1. Fork the frontfriend-contributions repository on GitHub.
  2. Create a branch named after your change: feat/stepper, fix/button-loading-a11y, etc.
  3. Add your contribution under contributions/<framework>/<name>/:
    • The component source file(s).
    • A meta.json with the fields above.
    • A README.md explaining the use case and key design decisions.
    • Optional: usage examples in examples/.
  4. Open a Pull Request against main and fill out the PR template.
  5. Review and iterate with the Frontfriend maintainers in the PR.
  6. Once approved, the maintainers merge your change into the official registry (packages/registry/components/<framework>/). It becomes available to everyone via npx frontfriend download on the next release.

Review Criteria

The Frontfriend team reviews contributions against the same standards as core components:

  • Semantic tokens only — no hardcoded colors, sizes, or spacing. See Design Tokens.
  • Accessibility — keyboard navigation, ARIA roles, focus management, screen reader support.
  • Theming — works in light and dark mode out of the box.
  • API consistency — props and naming follow existing component conventions (e.g. variant, size, asChild).
  • No third-party styling — Tailwind CSS only, no inline styles or CSS-in-JS.
  • Type safety — full TypeScript typings for props and return values.

What Doesn't Belong in Contributions

The contribution repo is for generic, reusable components. Do not submit:

  • Components that depend on your product's data shape or backend.
  • Components hardcoded with your brand identity.
  • Forks of existing components with cosmetic differences only — open a discussion first.

If you are unsure whether your change is a fit, open an issue in the contribution repo before writing code. The maintainers will help you decide whether the change belongs upstream or as a private customization.


Decision Tree

Not sure which path to take? Use this quick reference:

SituationPath
The change is specific to your product or brandingLocal fork with prefix
You need it shipped today and cannot wait for reviewLocal fork with prefix
The change is a general improvement (a11y, perf, new variant)Contribute via PR
You built a generic component that does not exist in the registry yetContribute via PR
You are not sureOpen an issue, ask first

Next Steps