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.
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
Personas

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
Discovery

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.
My Process

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.

Recruiter10 nav items
  • Dashboard
  • Candidates
  • Pipeline
  • Interviews
  • Jobs
  • Companies
  • Team
  • Calendar
  • Reports
  • Settings
Hiring manager6 nav items
  • Dashboard
  • Candidates
  • Pipeline
  • Interviews
  • Calendar
  • Settings
Admin11 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 receivedAdd to CandidatesAssign to roleStage: AppliedReview CVStage: ScreeningSchedule callShortlist or RejectNotify hiring manager
Hiring manager — mobile approval flow
Email notificationOpen mobile appView shortlisted candidatesReview each profileAdd feedback noteApprove / DeclineRecruiter notifiedNext stage triggered
End-to-end pipeline
Job createdCandidates sourcedScreening stageInterview stageAssessment stageOffer stageHiredJob closedReport 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
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.
What I Delivered

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.
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
Mockups

Desktop screens

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

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

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

Candidates — mobileOpen it full screen
Clever Recruit — Mobile Candidates ListClever Recruit — Mobile Candidate DetailClever Recruit — Mobile Candidate Profile
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

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
Technology

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
Style Guide

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
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