Thanks for taking the time to contribute to Creditra Frontend. This guide is the rules of the road: branching, commits, PR conventions, review checklists, and the discipline that keeps the design system intact.
If you're new to the codebase, read ARCHITECTURE.md and
DESIGN_SYSTEM.md first. They tell you where things go and why.
- Feature branches off
main. - Use a short kebab-case prefix that mirrors the conventional-commit category:
feat/draw-credit-confirmfix/landing-cta-focus-ringrefactor/wallet-context-reducerdocs/architecture-data-flowtest/amount-validation-edge-caseschore/upgrade-vite
Branch names show up in PR lists; treat them as documentation.
We use Conventional Commits. The subject is ≤ 72 characters, imperative mood, no trailing period.
| Type | Use for |
|---|---|
feat: |
User-visible new capability |
fix: |
Bug fix |
refactor: |
Non-behavioural code change |
perf: |
Performance improvement that doesn't change behaviour |
docs: |
Documentation only |
test: |
Tests only |
chore: |
Tooling, dependencies, repo hygiene |
style: |
Formatting, whitespace, no logic change |
revert: |
Revert a previous commit |
Optional scope in parentheses for clarity: feat(draw): add APR + total-cost to preview.
One logical change per commit. If your commit message includes "and", you probably want two commits. Reviewers can read 20 lines of focused diff; they cannot read 800 lines of mixed concerns.
Use the body to explain why the change was made and what trade-offs you considered. The what is in the diff. The why survives the diff.
- Title in conventional-commit form (matches the squash subject).
- Body includes:
- Summary — what changed and why
- Screenshots / GIFs for any UI change (before + after)
- Test plan — bulleted checklist of how you verified the change
- Linked issue —
Closes #123orRefs #456
- Keep PRs small and focused. 200 lines of diff or fewer is the sweet spot. If a feature genuinely needs more, split it: introduce types and tests first, then wire up the UI, then add the polish.
Reviewers paste this into their first review comment:
### Review checklist
- [ ] PR is one logical change, not a stack
- [ ] Conventional-commit title
- [ ] Tests added or updated for new behaviour
- [ ] Screenshots for UI change
- [ ] No new TypeScript errors (`npm run build` passes)
- [ ] Lint clean (`npm run lint`)
- [ ] Existing test count is neutral or higher
### Accessibility check (UI changes only)
- [ ] Keyboard navigation works (Tab, Shift+Tab, Enter, Escape)
- [ ] Focus indicators are visible
- [ ] Contrast ratios meet WCAG AA (4.5:1 text, 3:1 large/icons)
- [ ] Touch targets are at least 44x44 px
- [ ] Semantic HTML; ARIA only where no native element fits
- [ ] prefers-reduced-motion is respected
### Design-token check (UI changes only)
- [ ] No new inline hex values; tokens used from src/index.css or src/utils/tokens.ts
- [ ] No new inline spacing values; CSS custom properties used (--space-*)
- [ ] New tokens, if any, are added to both src/utils/tokens.ts and Design System/tokens.mdWe squash-merge to main. The PR title becomes the squash commit subject; the PR body
becomes the squash body. Title hygiene matters because it is what git log shows.
A new component must not introduce a new color, spacing value, or radius. The tokens exist exactly so that components stay visually consistent.
- If the value already exists in a token: use the token.
- If the value doesn't exist but is genuinely needed: add it to
src/index.css(CSS variable) andsrc/utils/tokens.ts(JS constant), document it inDESIGN_SYSTEM.md, and update../Design System/tokens.mdin the same PR. - If the value exists but doesn't fit the new use: the design system needs a conversation, not a one-off override.
Hard-coded hex values in a CSS file or style={{ color: '#abc' }} are a review blocker.
UI changes must pass the accessibility checklist in section 3. See
ACCESSIBILITY.md for per-pattern guidance.
- Every modal composes
useFocusTrap+useBodyScrollLock+useInertBackdrop. - Every icon-only button has
aria-label. - Color is never the sole signal.
- Touch targets ≥ 44 × 44 px.
npm run lint
npm test -- --run
npm run buildIf any fail, fix the underlying problem rather than silencing it.
- Lint failure — fix the code, don't add
// eslint-disable-next-line. - Test failure — fix the code or the test (whichever is wrong); never
.skipa test to make a PR green. - TypeScript error — fix the type, don't reach for
as anyor@ts-ignore. The project setsstrict: truefor a reason.
Exception: when working on docs-only or comment-only PRs, you may inherit existing
failures from main. You may not worsen them. The neutral-or-positive bar from the
checklist applies.
- Discriminated unions for state machines (
ConnectionStatus,WalletError,NotificationType). typefor object shapes;interfaceonly when declaration merging is needed.- Component props in the same file as the component (
interface Props { … }). - Public utility return types are explicit — don't rely on inference for an exported function.
| Kind | Convention | Example |
|---|---|---|
| React component file | PascalCase .tsx |
WalletButton.tsx |
| Hook file | camelCase .ts with use prefix |
useFocusTrap.ts |
| Util file | kebab-case .ts or camelCase .ts |
amount-validation.ts or tokens.ts |
| Type file | kebab-case .ts with .types suffix where it disambiguates |
draw-credit.types.ts |
| Test file | matches the file it covers, .test.ts(x) |
WalletButton.test.tsx |
| CSS module / stylesheet | matches the component | WalletButton.css |
Co-locate test and CSS files with the code they belong to.
@/is the alias forsrc/. Prefer absolute aliased imports over deep relative paths (../../../).- Import order: React → third-party →
@/→ relative./. - One import per line in long lists.
- Don't import from
index.tsbarrels we haven't introduced — there are none today, and we'd like to defer the decision.
- Every exported component, hook, or utility has TSDoc explaining purpose, params (or props), return shape, and side effects.
- If your change touches a documented decision (e.g. why we have an onboarding stepper),
update
UX_RATIONALE.mdin the same PR. - New tokens go into both
src/index.css(orsrc/utils/tokens.ts) andDESIGN_SYSTEM.md. - New a11y patterns go into
ACCESSIBILITY.md. - New performance trade-offs go into
PERFORMANCE.md.
Docs are part of the product. PRs that ship code without docs are incomplete.