Careers Page Template in React: Apply Form and JobPosting
A careers page has one job: turn a curious visitor into an application you can answer. Most careers page templates stop at the look. They give you a hero, a values grid and a list of roles, and leave out the parts that decide whether anyone applies or whether Google lists your jobs at all. This guide covers a careers page template in React and Next.js that does the whole job: a mission, the team, benefits, filterable roles, a job drawer, an apply form, a page per role and JobPosting structured data. The UI is our Careers Page template, so we are not neutral, but the data model, the routes and the markup work with any job page template.
What should a careers page template include?
A careers page template should answer, in order, the questions a candidate has before applying: why the company exists, who they would work with, how people work and what they get, how hiring works, which roles are open at what pay, and what to do when no role fits. When we searched "careers page template" on November 6, 2026, the results were mostly no-code and page-builder templates (involve.me, WeWeb, Notion, Webflow's careers list, a Framer job board theme), a printable worksheet and the career site builders inside applicant tracking systems such as Workable. Variations like "careers page template react", "careers page design template" or "careers page example" turn up more of the same kind of page. They are good at layout. Almost none of them say where the application goes or how a role shows up in Google's job results.
Here is what each section is for, and the prop that controls it in CareersPage:
Two of these carry more weight than they look. Group roles by team: Workable's careers page UX guide (opens in a new tab) (updated September 2023) recommends it, and it is what groupByDepartment does while All is selected. And show the salary range, because in several US states the law requires it: Paycor's state list (opens in a new tab) (updated November 20, 2025) shows California, Colorado, Illinois, New York and Washington requiring pay information in job postings. Colorado applies it to every employer; the other four set a minimum employer size (4 employees in New York, 15 in the rest). Check the rules for every state you hire in.
How do you install the careers page template?
One command copies both templates and everything they import. They are Pro items, so the account behind the CLI needs Wingo UI Pro.
You get components/templates/careers-page.tsx and careers-page-data.ts, job-detail-page.tsx and job-detail-page-data.ts, and their parts: the free Drawer for the job panel, the free Search Input for the role filter, the free Empty State for "No roles match those filters", the File Upload for the resume and the Team Section. With no props, the page renders nine sample roles of a made-up company called Acme.
$ npx wingo-ui@latest add careers-pageThe list does three things a static list does not. Search, department chips and a location select narrow the roles as you type, and each chip shows how many roles it matches. A click on a role opens a drawer with the description, the requirements, your benefits and the form, so a visitor can read three roles without losing the filter. And the row is still a real link to /careers/<slug>: Cmd or Ctrl click opens the page, and crawlers follow it.
How do you replace the sample content?
Keep your roles in one typed file that the list, the role pages and the structured data all read. The JobDetail type of the job page already holds everything the careers list needs, so one array feeds both templates. Add the two things structured data needs that a display row does not: where applicants may live for remote roles, and the office address for the rest.
Then render the list from a small client wrapper, because onApply is a function and the router link is a client component:
content.ts holds your values, perks, team, moments and steps, typed with CareersValue, CareersPerk, CareersTeamMember, CareersMoment and CareersStep from careers-page-data.ts. Go through the default labels too. The sample copy promises things like "We reply to every application within five working days" and a paid take-home. Those are fine promises if you keep them and a bad first impression if you do not. The sample numbers (42 teammates, 11 countries) are why showStats={false} is in the example: turn it back on when you have figures you are proud of.
Why does every role need its own page?
The drawer is a shortcut for people. The page is for everyone else: the link a recruiter pastes into a message, the URL your ATS emails, and the only place Google accepts job markup. Google's job posting structured data guide (opens in a new tab) (checked November 6, 2026) says JobPosting must only be used on pages that contain a single job posting, and not on pages that list jobs. So the careers list carries no JobPosting, and each /careers/<slug> page carries exactly one.
In Next.js 16, params is a promise, so both functions await it. The JSON-LD script sits in the server page, so it is in the first HTML response for crawlers that never run JavaScript. Our Next.js JSON-LD guide explains the < escape and how to keep markup in sync with the page.
$ npx wingo-ui@latest add job-detail-pageHow do you add JobPosting structured data for Google jobs?
Google requires five properties: title, description (the full description as HTML), datePosted, hiringOrganization and jobLocation, and a fully remote role may replace jobLocation with applicantLocationRequirements. The ones worth adding are validThrough, employmentType, baseSalary, identifier, directApply, and for remote roles jobLocationType with applicantLocationRequirements. Build them from the same Job the page renders, so the markup never says something the reader cannot see:
A few rules from the same Google guide decide whether this works:
TELECOMMUTEis only for roles that are 100% remote, and the description must clearly say so, so put that sentence inabout. A hybrid role with two office days keeps its office address injobLocation, which is why theJobtype makesofficerequired for anything that is not Remote.- A remote role needs at least one country applicants may live in, through
applicantLocationRequirements. baseSalaryis the employer's actual range, withunitTextasHOUR,DAY,WEEK,MONTHorYEAR(case-sensitive).- When a role is filled, take it down: a 404 or 410, no markup, or a
validThroughin the past. Deleting it fromJOBSdoes the first, because the route only knows the slugs inJOBSand callsnotFound()for the rest. Google warns that stale jobs can earn a manual action.
Google lists the countries where job results appear; it includes all of North America and Latin America, plus the United Kingdom, Germany, France and others in Europe. Test a deployed role page with the Rich Results Test (opens in a new tab) before you count on it.
Where should the applications go?
Applications go wherever your onApply handler sends them: a route handler on your own server that stores them or forwards them to your ATS. Without onApply, both templates wait 900 ms and show the confirmation. That is right for a demo and a silent failure on a live site, so wire it before launch. The helper below works for both templates, because their application objects share these fields. The job page also sends consent, the answer to its "keep my application for 12 months" checkbox, which you should store with the application:
Send it to a route handler, not a Server Action. The resume field accepts PDF and Word files up to 10 MB, and the Next.js 16 docs cap Server Action requests at 1 MB unless you raise serverActions.bodySizeLimit. Check your host's request size limit as well.
If you already use an ATS, let it stay the source of truth. Greenhouse is a good example: its Job Board API (opens in a new tab) (checked November 6, 2026) serves your jobs publicly with no authentication, and content=true adds the full description, department and office, so JOBS can be a fetch at build time. Its application endpoint needs your Job Board API key over Basic Auth, and the docs say to proxy form posts through your own server. That is the comment in the handler above. The Next.js contact form guide covers spam protection and email delivery for the same kind of handler.
How does the careers page behave on a phone?
On a phone the templates change shape instead of shrinking. The department chips scroll sideways in one row below 640px. Below 768px the location select opens a bottom sheet. The job drawer becomes a bottom sheet you drag down to close, and the resume field takes a tap to pick a file. On the job page, the sticky apply card turns into a bottom bar below 1024px, above the safe area, and only while the form is out of view.
The keyboard works too: / focuses the search field, Enter on a role opens the drawer, Escape closes it, and on the job page a failed submit moves focus to the first field with a problem. Role rows are real links, so screen readers announce them as links and middle click opens a new tab.
Should the team section live on the careers page or the about page?
Both, with different jobs. On the careers page, the team answers "who would I work with?", and the bold design swaps the photo mosaic for the Team Section with its "We're hiring" tile. On your about page, the same block links back to careers and counts the open roles:
$ npx wingo-ui@latest add team-sectionSet hiring={false} when nothing is open, so the tile does not invite people to an empty list. Our SaaS website template guide covers how careers fits with the other pages of a company site, and the build category has more screens built the same way.
What are the common careers page mistakes?
- Sample copy left in. Acme's 40,000 teams and its five-day reply promise are not yours. Search the labels for "Acme" before launch.
- No salary ranges. Several US states require them in job postings, and a range in the list saves an interview round for both sides when the pay does not fit.
- Expired roles still live. One array, one 404 when a role leaves it.
- JobPosting on the list page. Google does not accept it there; one posting per role page.
- An apply form that goes nowhere. Test
onApplywith a real file, and make it reject on failure so the form says so.
Next step
Click through the Careers Page and the Job Detail Page in all their designs, then install them with npx wingo-ui@latest add careers-page job-detail-page and replace the sample roles with your own. Both templates and the Team Section are part of Wingo UI Pro, $8 a month, $80 a year or $150 once (pricing). A free account gets the free parts, like the Drawer and Search Input, if you would rather build the page yourself.
FAQ
What should a careers page include?
A one-paragraph mission, the team, your values and benefits, how you hire, and the open roles grouped by department with location, type and salary range. Each role needs its own page with the full description and an apply form, plus an open application for people who do not see a fit.
Can I add JobPosting structured data to the careers list page?
No. Google's job posting documentation says the markup must only be used on pages that contain a single job posting, and not on pages that list jobs. Put it on each role page, such as /careers/senior-frontend-engineer.
How do I mark a remote job for Google for Jobs?
Set jobLocationType to TELECOMMUTE and list the countries applicants may live in under applicantLocationRequirements. Google only accepts TELECOMMUTE for fully remote roles, so a hybrid role keeps its office address in jobLocation.
What happens to a job posting after the role is filled?
Remove it: return a 404 or 410 for the page, remove the markup, or set validThrough to a past date. Google warns that leaving expired jobs up can lead to a manual action.
Is the Wingo UI careers page template free?
No. The Careers Page, the Job Detail Page, the Team Section and the File Upload they use for resumes are Pro items. Other parts they build on, like the Drawer, Search Input, Empty State and Marketing Section, are free.
- Careers
- Templates
- Next.js
- Structured data