Thanks for contributing. This app is the public showcase for the builders, projects, and teams on Boundless. It is display-only: it presents data that is created and managed in the main Boundless app.
The single most important thing to understand before you write any UI:
This app must look and behave like a native part of Boundless. We do not invent new visual design here. We follow the shared design system and reuse the shared components. When something is missing, we port it from boundless-platform rather than making up our own.
Please read this whole guide before your first pull request.
- Golden rules
- Local setup
- Follow the design system
- Reuse components before building new ones
- Component inventory
- Patterns and conventions
- Code style
- Branches, commits, and PRs
- Pull request checklist
- Follow the design system. Read design.md and use the tokens and primitives it documents. No hard-coded colors, fonts, or spacing when a token exists.
- Reuse before you build. Check
components/first. If a button, card, dialog, or section already exists, use it. Do not hand-roll a second version. - Keep it display-only. This app reads and renders data. Creating, editing, and uploading belong in the main Boundless app. Do not add creation forms or write flows here.
- When in doubt, copy from boundless-platform. It is the source of truth for look, feel, and component behavior.
- House style: no em dashes in code, comments, or copy. Use periods, commas, or rewrite.
Requires Node >=20.19.
npm install
npm run devOpen http://localhost:3000. The page hot-reloads as you edit.
| Command | What it does |
|---|---|
npm run dev |
Start the dev server (Turbopack) |
npm run build |
Production build (also runs the TypeScript check) |
npm run start |
Serve the production build |
npm run lint |
Run ESLint |
The full reference is design.md. The essentials:
- Colors. Use the token utilities:
bg-primary-500,text-muted-foreground,border-border,bg-card, and the semantic tokens. Do not paste raw hex when a token exists. The brand teal isprimary-500(#2eedaa). - Typography. Headings use
font-headingand thetext-h1..text-h6/text-display-*scale. Body uses the default sans (text-body,text-body-sm). Do not set arbitrary font sizes. - Theme. The app is dark by default. Style with semantic tokens so both themes work. Do not assume a light background.
- Spacing and radius. Use the Tailwind scale and the shared radii
(
rounded-lg,rounded-xl,rounded-pill). Page content caps atmax-w-page(1440px). - Icons. Prefer the design-system icon library in
components/icons(Linear and Bold styles). Uselucide-reactonly where a library icon does not exist.
Avoid arbitrary Tailwind values (w-[137px], text-[#123456]) unless you are
matching an exact spec that has no token. If you find yourself reaching for one
often, that is a sign a token or component is missing. Raise it.
Before you build any UI, search components/. The order to work
in:
- Is there a primitive for it? Look in
components/ui(buttons, inputs, dialogs, cards, tabs, and so on). Compose it with props andclassName. - Is there a section or layout piece? Look in
components/marketing(hero background, sections, CTAs, FAQ) andcomponents/layout(header, footer). - Is there a card? Look in
components/cards. - Still missing? Port it from boundless-platform, keeping its structure and styles, then adapt it to this app (usually by decoupling it from data fetching so it takes plain props). Only build from scratch as a last resort, and match the design system when you do.
Customize primitives through their variants and className, not by copying and
editing. For example, the Button composes intent x appearance x size x
shape:
import { Button } from '@/components/ui/button';
<Button>Primary</Button>
<Button intent='secondary' appearance='outline' size='small'>Secondary</Button>
<Button intent='destructive'>Delete</Button>A quick map of what already exists so you can reuse it.
button, card, input, textarea, label, checkbox, select, dialog,
popover, dropdown-menu, hover-card, command, tabs, avatar,
avatar-group, pagination, skeleton, sonner (toasts), form,
country-select, image-cropper, option-card, snapshot-card, entity-row,
glow-banner, stats-bar, token-amount, success-dialog (the reusable
success modal), sparks-icon.
site-header (nav), site-footer + footer-glow, pill-button,
boundless-logo (BoundlessLogo lockup and BoundlessMark), brand-icons
(social and GitHub glyphs).
hero-background (the animated hero gradient), glow, section
(Section / SectionHeading / Eyebrow), reveal, cta-section, cta-pill,
cta-ticker, marketing-button, partner-logos, pillar-card, personas,
testimonials, four-steps, trust-bar, faq + faq-accordion,
prize-pool, funding-paths, and more.
opportunity-card (the project / opportunity display card) and its skeleton.
It takes an OpportunityCardView prop, so it is fully presentational.
The full design-system icon library, ~410 icons in Linear and Bold styles. Same
ergonomics as lucide: color from currentColor, size from Tailwind.
import { BellIcon, BellBoldIcon } from '@/components/icons';
<BellIcon className='size-5 text-foreground/80' aria-hidden />- Imports. Use the
@/path alias (@/components/ui/button,@/lib/utils). cn(). Merge class names withcnfromlib/utils. It resolves Tailwind conflicts (last wins).- Variants. Build component variants with
class-variance-authority(cva), following the pattern incomponents/ui/button.tsx. - Server vs client. Components are server components by default. Add
'use client'only when you need state, effects, or browser APIs. - Routes stay thin. Files in
app/compose components. Keep logic in components and helpers, not in route files. - Display-only data. This app renders data shaped elsewhere. Card and view components should take plain props (view models), not fetch or transform API data themselves.
ESLint owns linting; run npm run lint. The house conventions:
- Single quotes, semicolons, 2-space indentation.
- JSX attributes use single quotes.
- No em dashes in code, comments, or copy.
- Write user-facing copy and comments in short, plain English. Many readers are not native speakers.
- Branch off
mainwith a descriptive name, for examplefeat/builders-gridorfix/footer-blend. - Commits follow Conventional Commits:
feat,fix,chore,docs,refactor,test,style. Example:feat(cards): add team card variant. - One focused change per PR. Keep it reviewable.
- Screenshots. Any PR that changes UI must include a screenshot (and a short clip for animation or interaction). Reviewers need to see the result.
Before you open a PR, confirm:
- I reused existing components and tokens where possible.
- New UI follows design.md (no stray hex, arbitrary sizes, or off-system spacing).
- The change is display-only (no creation or upload flows).
-
npm run lintpasses. -
npm run buildpasses (this also type-checks). - I attached a screenshot for any visual change.
- No em dashes in code, comments, or copy.
Thanks again for helping make Boundless Builders great.