Recruitment · B2B SaaS · End-to-End Product Design

Clever Recruit — High-Density Pipeline for 200+ Candidates

I designed a B2B recruitment platform and then built it. Research and information architecture, then Figma prototypes and a component library, then the React front end. Building it is how I found out which of my design decisions actually held up.

Role
UI/UX Designer
Scope
Research → Design → Code
Year
2026
Platform
Full-Stack Web App
Design targets
< 3 clicks
From the dashboard to any action in the pipeline. That path gets walked all day, so I counted it.
20+ rows
On screen at once, colour-coded by stage. You can read the state of a role without scrolling.
2 platforms
Desktop for recruiters, mobile for hiring managers. Two different jobs, so I stopped trying to serve both with one layout.

Research, design and the React front end

This was a self-directed concept with no client, taken from a blank page to a working app. Everything below is mine:

  • Research — teardowns of five existing ATS products, used to pressure-test the assumptions behind the design.
  • Information architecture — the three zones, the permission boundaries, and the navigation for 200+ candidates without the screen collapsing into noise.
  • Interface design — wireframes, a component library in Figma, and the high-density screens themselves.
  • Front-end development — the React build, where several Figma decisions had to be revisited against real empty states, errors and filters that return nothing.
Overview

Density as a design value

Clever Recruit is a B2B recruitment platform concept I took from problem definition to a working front end. One question ran through the whole thing: how do you put 20+ rows on screen at once, across a pipeline of 200+ candidates, and still make the screen readable to a hiring manager who logs in twice a month?

I did all of it. Teardowns of five ATS products first, then the personas, the information architecture, wireframes, and a component library in Figma. Then I built the front end in React. That is where the states I had waved past in Figma stopped being optional: empty tables, error paths, a filter that returns nothing.

Delivered screens
  • Dashboard
  • Candidates (full CRUD + filtering)
  • Jobs (lifecycle management)
  • Companies
  • Team
  • Calendar/Interviews
  • Reports/Analytics
  • Settings (profile, appearance, notifications)
  • Authentication
  • Dark mode
  • Global search (⌘K)
DiscoveryCompetitive teardown · Persona creation · Jobs-to-be-done mapping · Assumption mapping
Information ArchitectureScreen mapping · User flows · Role-based IA · Navigation systems
UI Design & SystemsFigma design system · Component library · Design tokens · Responsive breakpoints
Interaction DesignMicro-interactions · State management · Dark/light modes · Keyboard shortcuts
Cross-PlatformDesktop-first density · Mobile companion · Responsive layouts · Touch targets
Front-End DevelopmentReact · TypeScript · Mantine UI · Design-to-code implementation
Clever Recruit — Overview

Density without losing an infrequent user

A recruiter living in the pipeline all day wants density: every candidate, every stage, visible at once, with no clicking to find out where someone sits. A hiring manager who logs in twice a month wants the opposite — a screen they can read cold, without having learned the system first. Most ATS tools I tore down picked one of those users and made the other one suffer. The problem this project set out to solve was fitting 200+ candidates and 20+ rows on screen at once without making the occasional user feel lost the moment they log in.

Research & Discovery

Designed for two users

Petra H.
Senior Recruiter · Daily power user
Goals
  • See all 200+ candidates across 15 roles at once
  • Move candidates through pipeline with minimal clicks
  • Get quick decisions from hiring managers without chasing them
Pain Points
  • Loses track of candidates across email threads
  • No single source of truth for pipeline status
  • Hiring managers respond slowly without structured prompts
Marko L.
Hiring Manager · Approver
Goals
  • Review shortlisted candidates quickly on mobile
  • Give structured feedback without navigating a complex system
  • Make hiring decisions without leaving the tool
Pain Points
  • Gets unstructured CV attachments via email with no context
  • No clarity on why a candidate was shortlisted
  • Mobile experience on most ATS tools is practically unusable

The assumptions this rests on

This was a self-directed concept. I didn't interview any recruiters, so nothing below is a finding. These are the assumptions I designed from, pulled out of ATS teardowns and out of how other dense B2B tools handle the same problems. I've written each one as a claim a real study could knock down, next to what I built on it:

AssumptionDesign Response
Recruiters need to scan 20+ candidates at once — density is a feature, not a problemTable view as default: sortable columns, status colour-coding, bulk actions. Every bit of padding I added cost a visible row.
Hiring managers primarily use the system for approvals, not pipeline managementMobile companion designed specifically for the approval use case — not a shrunken desktop.
Status is the primary organiser in every recruiter's mental modelStatus as the dominant visual element — colour-coded, always visible, sortable by default.
Bulk operations are essential for recruiters processing high daily volumesBulk select with checkbox column; bulk action toolbar appears on any selection.
Saved filters are essential for recruiters managing multiple simultaneous rolesPersistent filters at session level; named filter presets for saved common views.

From assumptions to working code

Five phases. Each one had to hand something concrete to the next, or it wasn't finished.

Discover
Discovery
  • ATS competitive teardown (5 tools)
  • Jobs-to-be-done mapping
  • Assumption mapping
  • Pain point synthesis
Define
IA & Strategy
  • Information architecture
  • Personas & user stories
  • Role-based navigation strategy
  • Cross-platform scope definition
Design
Wireframes & UI
  • Low-fi wireframes (desktop + mobile)
  • Design system & component library
  • High-fidelity mockups
  • Interaction specifications
Validate
Test & Iterate
  • Self-walkthrough against the recruiter task list
  • Mobile pass on the hiring-manager views
  • Interaction refinement
  • Accessibility review
Deliver
Build & Ship
  • React front-end implementation
  • Design-to-code validation
  • Edge case resolution
  • Deployed build for review
Information Architecture

Every screen mapped

Screen / StatePurposeKey elements & implementation
DashboardPipeline overview & metricsStat cards (candidates, jobs, interviews, hires) · Recent activity feed · Clickable navigation to detail pages
CandidatesFull candidate database with CRUDSortable table with colour-coded status · Create/Edit/Delete modals · Filter by stage · Detail drawer with timeline · Search
JobsActive role managementJob table with company logos · Create/Edit/Delete · Close/Reopen actions · Detail drawer with candidate count
CompaniesCompany profiles & managementCard grid with live logos (DeBounce API) · Add/Edit/Delete · Detail drawer · Open positions count
TeamTeam member directoryMember cards · Clickable detail drawer with stats · Managed candidates list · Role-based display
Calendar / InterviewsInterview schedulingMonth calendar view · Navigation arrows · Today button · Schedule interview modal · Event indicators
Reports / AnalyticsPipeline analyticsTime period selector · Pipeline funnel · Source effectiveness · Time-to-hire metrics · CSV export
SettingsUser & app configurationProfile management with avatar upload · Notification preferences · Appearance (light/dark) · Password change with validation
Login / AuthAuthentication & access controlJWT-based auth · Protected routes · UserContext for global state · Persistent sessions
App ShellGlobal layout & navigationCollapsible sidebar · Global search (⌘K) · Notifications panel · Light/Dark mode toggle · Responsive mobile drawer

Three zones, drawn as they actually route

The table above is every screen, flat. Grouped by who can reach it, it is three zones and one gate between them.

Public — unauthenticated
  • /careersJobs directory — browse, filter, search
  • /careers/:jobIdJob detail — closed roles get their own state, not a 404
  • /careers/:jobId/applyApplication form + CV upload
  • /careers/statusStatus lookup by email + reference
  • /loginAuthentication
Authenticated — recruiter & hiring manager
  • /dashboardRole-aware overview
  • /jobsDraft → preview → publish lifecycle
  • /candidatesFull table + detail drawer
  • /pipeline/:jobIdKanban board, bulk actions
  • /interviews · /calendarScheduling, shared across both views
Administrator
  • /admin/usersInvite, list, change role
  • /admin/rolesPer-user role assignment
  • everything aboveRecruiter surface, plus the two routes here

Permission boundaries

A permission-denied attempt renders a dedicated state — never a blank page or a silent redirect.

ActionRecruiterHiring ManagerAdminCandidate
Create, edit or close a job✓—✓—
View candidates for a job✓✓✓—
Change a candidate’s stage✓—✓—
Leave feedback on a candidate✓✓✓—
Schedule an interview✓—✓—
Invite a user or change a role——✓—
Apply to a job———✓
View own application status———✓

What the sidebar actually shows

The zones above are what each role can reach. The sidebar is a stricter subset — items are hidden, not just guarded, so nobody sees a link a route guard would then turn away.

Recruiter — 10 nav items
  • Dashboard
  • Candidates
  • Pipeline
  • Interviews
  • Jobs
  • Companies
  • Team
  • Calendar
  • Reports
  • Settings
Hiring manager — 6 nav items
  • Dashboard
  • Candidates
  • Pipeline
  • Interviews
  • Calendar
  • Settings
Admin — 11 nav items
  • Dashboard
  • Candidates
  • Pipeline
  • Interviews
  • Jobs
  • Companies
  • Team
  • Calendar
  • Reports
  • Adminuser list · invite · role change
  • Settings
User Flows

Step-by-step journeys

Recruiter — processing a new applicant
Applicant received→Add to Candidates→Assign to role→Stage: Applied→Review CV→Stage: Screening→Schedule call→Shortlist or Reject→Notify hiring manager
Hiring manager — mobile approval flow
Email notification→Open mobile app→View shortlisted candidates→Review each profile→Add feedback note→Approve / Decline→Recruiter notified→Next stage triggered
End-to-end pipeline
Job created→Candidates sourced→Screening stage→Interview stage→Assessment stage→Offer stage→Hired→Job closed→Report generated

Six of these carry a decision

The three above are the happy path. These six are where a wrong assumption would actually show up — the branch, and what each side of it does.

Recruiter creates and publishes a job

Jobs → New job
Title, company, location, type, salary, description
Publish now, or save as draft?
If notSaved as a draft — editable, not visible on /careers
If soStatus: open — live on the public careers site immediately

Candidate applies to a role

Job detail → Apply
Name, email, phone
CV upload, optional note
Has this email already applied to this job?
If notSubmits — confirmation screen with a status-check link
If so“You’ve already applied” — links to status, not a second entry

Candidate uploads a CV

Inside the application form
Select or drag a file
PDF / DOC / DOCX, under 5MB?
If notNames the actual problem — type or size limit — form stays filled in
If soAttached, filename and size shown, rest of the form still editable

Recruiter bulk-moves a pipeline stage

Pipeline board, multiple selected
Choose an action from the sticky toolbar
Is a selected candidate already at the target stage?
If notAll selected records move together
If soExcluded from the move automatically, the rest still proceed

Recruiter schedules an interview

Candidate detail, /interviews, or Calendar
Interviewer(s), date, time, type
Is the interviewer already booked at that time?
If notSaves without a warning
If soWarning shown — can still schedule anyway, not a hard block

Admin changes a user’s role

Admin → Users → select a user
Change the role dropdown
Is this the organisation’s last remaining admin?
If notRole updates, reflected immediately
If soBlocked, with the reason stated — not a silent failure
Lo-fi Wireframes

Screen-level design

Desktop

Clever Recruit — Candidates Table WireframeClever Recruit — Candidate Detail WireframeClever Recruit — Candidate Profile Wireframe

Mobile

Clever Recruit — Mobile Candidates WireframeClever Recruit — Mobile Detail WireframeClever Recruit — Mobile Profile Wireframe
Edge Cases

The states between empty and perfect

A screenshot only ever shows the happy path. These are the states I designed for instead — most are live in the build below; a few are specified for whoever extends this next.

Empty & not-found states
No open rolesIn the build

“No open roles yet” on /careers, a “create your first job” prompt on /jobs — never an unexplained empty table.

A filter returns nothingIn the build

A distinct no-results state on /candidates, separate from the true-empty state.

Candidate is archivedSpecified

De-emphasised and filterable everywhere it appears — never deleted outright.

Forms & validation
Required field missingIn the build

Inline, field-level error on blur or submit — submit stays blocked until it’s resolved.

Invalid email or phoneSpecified

A specific message, not “invalid input” — and phone accepts international formats.

Long names or long textSpecified

Ellipsis + tooltip for names; line-clamp + “read more” for longer notes and descriptions.

Duplicates & conflicts
Candidate applies twiceSpecified

Caught by email + job match — “you’ve already applied”, not a silent duplicate row.

Interviewer double-bookedSpecified

A warning, not a hard block — scheduling anyway is sometimes the right call.

Job closes mid-applicationSpecified

Submission is blocked with a clear message; nothing the candidate typed is lost.

Files & failure states
No CV attachedSpecified

Required by default — an optional toggle per job is future scope, not built.

Wrong file type or too largeSpecified

Names the actual limit, e.g. “max 5MB — yours is 8MB”, not a generic rejection.

Any save failsIn the build

A toast, and the data already entered is never cleared while it retries.

Access, accessibility & scale
Insufficient permissionsSpecified

A dedicated permission-denied state — never a blank page or a silent redirect.

Last remaining adminIn the build

A role change that would leave zero admins is blocked, with the reason stated.

200+ candidates in one listIn the build

Pagination on the candidates table; the pipeline board still needs the same treatment.

Reduced-motion preferenceSpecified

Not yet verified against Mantine’s default transitions — the one honest gap in this list.

Design Rationale

Every choice justified

A screenshot won't tell you why anything is the way it is. So here's the reasoning behind each of the core decisions, and the option I turned down in each case.

Decision MadeWhy — RationaleAlternative Rejected
Table view as defaultRecruiters with 200+ candidates need density. A table shows 20+ rows simultaneously with sortable columns. Whitespace is a liability for power users.Kanban as default — available as a per-job view for hiring managers, but too sparse for the master candidate list.
Status colour-codingYou have to be able to read stage while scanning 50 rows in half a second. Colour does that on its own.Text-only status labels. Rejected — a label has to be read row by row, which is exactly the cost I was trying to get rid of.
Mobile as approval toolSqueezing the whole desktop tool onto a phone makes a dense B2B product unusable. The hiring manager isn't running the pipeline anyway, so mobile does the one thing he came to do.Responsive desktop. Rejected — a table-based workflow loses the thing that makes it useful the moment you shrink it.
Persistent filtersIf you filter down to one role, you want to still be in that role after opening a candidate and coming back. Resetting on every page load is a small annoyance you pay over and over.Reset-on-navigate. It's the common pattern, and it's the wrong one for someone who filters once and lives in that view all day.
Bulk actionsRecruiters process candidates in batches after screening calls. One-at-a-time stage changes for 15 candidates is 15× the work.Individual actions only. Simpler to build, and it falls over on the exact job recruiters do most.
Interactive Prototype

Interactive walkthrough

The live desktop app is below. Move a candidate through the pipeline, open a job, try the global search. The hiring manager's mobile flow is in the Mobile section above.

Interactive prototype

The prototype below is the running build — click it to interact.

Tap the menu to navigate on mobile · Open in new tab

The pipeline, screen by screen

Captured from the running build. The candidates table is the centre of gravity — everything else is built to get a recruiter back to it.

Dashboard
01Dashboard
Candidates table
02Candidates table
Candidate detail — paged 1/13 without leaving the table
03Candidate detail — paged 1/13 without leaving the table
Jobs
04Jobs
Companies
05Companies
Calendar
06Calendar
Reports
07Reports
Team
08Team
Settings
09Settings
Final Design

Desktop screens

Clever Recruit — Candidates TableClever Recruit — Candidate DetailClever Recruit — Candidate Profile Expanded

Hiring manager experience

Mobile does one job: the hiring manager's approval loop. Read the shortlist, leave a note, approve or decline. Everything else stays on desktop. Tap through the live prototype below.

Interactive prototype
Candidates — mobileOpen it full screen ↗
Clever Recruit — Mobile Candidates ListClever Recruit — Mobile Candidate DetailClever Recruit — Mobile Candidate Profile
Design System

Brand system

The product’s own theme, not a moodboard: five colour tuples straight out of theme/index.ts, Roboto at weight 700 for headings, and one radius — md — shared by every button, input, select and card.

Blue — primary
#1c7ed6
Gray — neutral
#868e96
Teal — positive
#12b886
Yellow — caution
#fab005
Red — destructive
#fa5252

The one system worth calling out: pipeline-stage colour. Dashboard, Candidates and the Reports funnel each carried their own map, and disagreed — Hired was green on two screens and red, the app’s own rejection colour, on the third. Reconciled into one map, used everywhere a stage renders:

StageColour
AppliedBlue
ScreeningCyan
InterviewTeal
AssessmentYellow
OfferOrange
HiredGreen
RejectedRed
WithdrawnGray

I took this one from a blank page to a deployed app on my own. I wrote the assumptions down first, built the information architecture around them, made the component library in Figma, then built it in React to find out where I was wrong. The limit worth naming: no recruiter has used this yet, so everything in the discovery table is still an assumption rather than a finding.

View source on GitHub

From design to code

LayerTechnologyWhy chosen
FrontendReact 18 · Vite · TypeScriptComponent-based architecture matches the design system; Vite for fast development iteration
UI LibraryMantine v7Accessible components and dark mode out of the box, so I wrote very little custom CSS
IconsTabler IconsConsistent stroke-based icon set; native Mantine integration
BackendNode.js · Express · TypeScriptSame language as frontend; Express for lightweight RESTful API
DatabasePostgreSQL · Prisma ORMType-safe database access; auto-generated migrations; relational data model suits recruitment pipelines
AuthJWT · bcryptjsStateless authentication; protected routes via React Context
StateTanStack Query · React ContextServer state caching with TanStack; global user/theme state via Context API
Outcome & Impact

What shipped, end to end

Design system delivered50+ Figma components with variants and tokens, documented well enough that someone other than me could build from them.
Design validated in codeI built the front end in React, 10 screens. That is where I found out which interactions I had specified badly.
What I Learned

What the build talked me out of

The pipeline started as a one-at-a-time stage dropdown because it was fast to wireframe. Inside a day of clicking through a working board with real candidate cards in it, that felt like the wrong shape entirely — which is why the Kanban board with drag-and-drop and a keyboard ‘Move to…’ menu exists now instead of what I’d first drawn. A wireframe can hide a bad interaction for weeks; a build can’t.

The other adjustment was scope, not interaction. I built a real backend — Express, Prisma, Postgres, JWT — and then didn’t deploy it, because proving the data model holds up was the point, not standing up infrastructure nobody would use yet. The live app runs in demo mode off the same client code; the backend exists to be checked, not to be hosted.

Trying to make one layout serve a recruiter working the pipeline all day and a hiring manager checking it between meetings kept producing compromises neither wanted. Splitting them — desktop for the deep work, mobile for the quick check — only happened after I’d tried to force them together first.

What this project demonstrates

I took this one from a blank page to a deployed app on my own. I wrote the assumptions down first, built the information architecture around them, made the component library in Figma, then built it in React to find out where I was wrong. The limit worth naming: no recruiter has used this yet, so everything in the discovery table is still an assumption rather than a finding. View source on GitHub

Next project
Loop —
From Proposal to Paid, Without Six Apps in Between
Get in touch

Let’s talk.

Happy to walk through any of these case studies on a call, including the parts that didn’t work.

Please add your name
Please add a valid email
Please add a subject
Please add a message
Open to opportunities
Based inRijeka, Croatia · remote worldwide, or relocating within the EU
Right to workEU citizen (Portugal) · no permit needed anywhere in Europe
Open toUI/UX designer roles, employed or contract
Email me directly atnicole@dinardo.design