ui-polish 5/6: a11y semantics and remaining labels

- All 28 <div className="form-section-title"> section markers are real
  <h3> headings now — the class carries every visual property, so this
  is semantics-only, zero visual change.
- Remaining icon-only .act-btn buttons labeled: CandidateProfile
  (download/edit note/revise scorecard, incl. one that had neither
  tooltip nor label), CandidateForms amend button.
- PasswordField show/hide toggle is keyboard-reachable (tabIndex -1
  removed) with aria-pressed state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/29/head
Talha Ahmed 2026-08-25 21:53:08 +05:00
parent af6ef715c5
commit c4793479a2
12 changed files with 41 additions and 40 deletions

View File

@ -35,7 +35,7 @@ export default function PasswordField({
className="pw-toggle"
onClick={() => setVisible((v) => !v)}
aria-label={visible ? 'Hide password' : 'Show password'}
tabIndex={-1}
aria-pressed={visible}
>
{visible ? (
<svg viewBox="0 0 24 24" aria-hidden="true">

View File

@ -75,7 +75,7 @@ export default function AiStudio() {
</div>
<div className="card" style={{ boxShadow: 'none', background: 'var(--bg-sunken)' }}>
<div className="card-body">
<div className="form-section-title" style={{ marginTop: 0 }}>API Contract (preview)</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>API Contract (preview)</h3>
<pre className="resume-thumb" style={{ maxHeight: 'none' }}>
{`POST /api/ai/${detail.name.toLowerCase().replace(/ /g, '-')}
{

View File

@ -308,7 +308,7 @@ export default function Assessments() {
<div className="mb-18"><ProgressBar pct={viewing.score} /></div>
{viewing.sectionScores.length > 0 && (
<>
<div className="form-section-title" style={{ marginTop: 0 }}>Section Breakdown</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>Section Breakdown</h3>
{viewing.sectionScores.map((s) => (
<div className="flex items-center gap-12" style={{ marginBottom: 10 }} key={s.label}>
<span style={{ width: 130, fontSize: 13 }}>{s.label}</span>

View File

@ -278,7 +278,7 @@ function FormRowList({ rows, defs, onEdit, canEdit }) {
</Badge>
)}
{canEdit && (
<button className="act-btn" data-tip="Amend this form" onClick={() => onEdit(r)}>
<button className="act-btn" data-tip="Amend this form" aria-label="Amend this form" onClick={() => onEdit(r)}>
<Icon name="edit" />
</button>
)}

View File

@ -413,13 +413,13 @@ export default function CandidateProfile({
<h3 style={{ marginBottom: 4 }}>{c.name}</h3>
<p className="text-muted">{c.currentTitle} · {c.location}</p>
<div className="divider" />
<div className="form-section-title" style={{ marginTop: 0 }}>Summary</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>Summary</h3>
<p className="text-muted">
Results-driven {c.currentTitle.toLowerCase()} with {c.experience} years of experience
across {c.department.toLowerCase()}. Passionate about building high-quality products
and collaborating with cross-functional teams.
</p>
<div className="form-section-title">Experience</div>
<h3 className="form-section-title">Experience</h3>
<div className="info-item">
<div className="iv">{c.currentTitle} {c.currentCompany}</div>
<div className="il" style={{ textTransform: 'none' }}>2021 Present</div>
@ -428,7 +428,7 @@ export default function CandidateProfile({
<div className="iv">Associate {priorCompany}</div>
<div className="il" style={{ textTransform: 'none' }}>2018 2021</div>
</div>
<div className="form-section-title">Education</div>
<h3 className="form-section-title">Education</h3>
<div className="iv">{c.education}</div>
</div>
</div>
@ -572,7 +572,7 @@ export default function CandidateProfile({
<Icon name="file" />
</span>
<div className="lr-main"><div className="lr-title">{d.n}</div><div className="lr-sub">{d.s}</div></div>
<button className="act-btn" onClick={() => toast(`Downloading ${d.n}`, 'info')}>
<button className="act-btn" data-tip="Download" aria-label={`Download ${d.n}`} onClick={() => toast(`Downloading ${d.n}`, 'info')}>
<Icon name="download" />
</button>
</div>
@ -921,7 +921,7 @@ function InterviewTab({ userId, inboxId, rows }) {
)}
<div className="divider" />
<div className="form-section-title" style={{ marginTop: 0 }}>Schedule an interview</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>Schedule an interview</h3>
<div className="form-grid">
<div className="form-field">
<label>Type</label>
@ -1065,7 +1065,7 @@ function NoteRow({ note: n, userId }) {
</div>
{mine && (
<div className="lr-right">
<button className="act-btn" data-tip="Edit note" onClick={() => setEditing(true)}>
<button className="act-btn" data-tip="Edit note" aria-label="Edit note" onClick={() => setEditing(true)}>
<Icon name="edit" />
</button>
</div>
@ -1124,7 +1124,7 @@ function ActivityTab({ userId, inboxId, rows }) {
)}
<div className="divider" />
<div className="form-section-title" style={{ marginTop: 0 }}>Log activity</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>Log activity</h3>
<div className="form-grid">
<div className="form-field">
<label>Type</label>
@ -1188,6 +1188,7 @@ function DocumentsTab({ rows, inboxId }) {
className="act-btn"
disabled={!inboxId || download.isPending}
title={!inboxId ? 'No application id for download' : 'Download'}
aria-label={`Download ${d.name}`}
onClick={() => download.mutate({ index: i, filename: d.name })}
>
<Icon name="download" />
@ -1301,7 +1302,7 @@ function FeedbackRow({ row: f, userId }) {
<div className="lr-right" style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
{f.review ? <Badge>{f.review}</Badge> : null}
{mine && (
<button className="act-btn" data-tip="Revise scorecard" onClick={() => setEditing(true)}>
<button className="act-btn" data-tip="Revise scorecard" aria-label="Revise scorecard" onClick={() => setEditing(true)}>
<Icon name="edit" />
</button>
)}
@ -1347,7 +1348,7 @@ function FeedbackTab({ userId, inboxId, rows }) {
)}
<div className="divider" />
<div className="form-section-title" style={{ marginTop: 0 }}>Submit a scorecard</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>Submit a scorecard</h3>
<div className="form-grid">
<div className="form-field">
<label>Recommendation</label>

View File

@ -560,7 +560,7 @@ export function AtsMatch({ candidate: c, jobTitle, onClose, onProfile }) {
</div>
</div>
<div>
<div className="form-section-title" style={{ marginTop: 0 }}>Assessment</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>Assessment</h3>
<p className="text-muted" style={{ fontSize: 13 }}>{critique ?? '—'}</p>
</div>
</div>
@ -570,9 +570,9 @@ export function AtsMatch({ candidate: c, jobTitle, onClose, onProfile }) {
<div className="info-item"><div className="il">Scored On</div><div className="iv">{scoredOn ?? '—'}</div></div>
</div>
<div className="form-section-title" style={{ marginTop: 0 }}>
<h3 className="form-section-title" style={{ marginTop: 0 }}>
Matched Skills ({matched.length})
</div>
</h3>
<div className="k-tags" style={{ marginBottom: 16 }}>
{matched.length
? matched.map((s) => (
@ -581,9 +581,9 @@ export function AtsMatch({ candidate: c, jobTitle, onClose, onProfile }) {
: <span className="text-muted"></span>}
</div>
<div className="form-section-title" style={{ marginTop: 0 }}>
<h3 className="form-section-title" style={{ marginTop: 0 }}>
Missing Skills ({missing.length})
</div>
</h3>
<div className="k-tags">
{missing.length
? missing.map((s) => (
@ -844,9 +844,9 @@ function AddCandidate({ onClose, onSave, onInvalid }) {
{/* Role selection sits directly above the CV because it is what the CV
gets scored against same card UI as Job Matching's role list.
(.req is scoped to `.form-field label .req`, so tint it here.) */}
<div className="form-section-title">
<h3 className="form-section-title">
Applied Job <span className="req" style={{ color: 'var(--danger)' }}>*</span>
</div>
</h3>
{posts.length > 0 && (
<div className="toolbar-search" style={{ maxWidth: 'none', marginBottom: 10 }}>
<Icon name="search" />
@ -892,9 +892,9 @@ function AddCandidate({ onClose, onSave, onInvalid }) {
)}
<FieldError>{form.errors.job}</FieldError>
<div className="form-section-title">
<h3 className="form-section-title">
CV / Resume <span className="req" style={{ color: 'var(--danger)' }}>*</span>
</div>
</h3>
<input
ref={fileInput}
type="file"

View File

@ -181,7 +181,7 @@ export function ScoredCandidateDetail({ candidate: c, jobTitle, onClose }) {
</div>
{completed ? (
<>
<div className="form-section-title" style={{ marginTop: 0 }}>AI Assessment</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>AI Assessment</h3>
<p className="text-muted">{c.critique ?? '—'}</p>
</>
) : (
@ -194,7 +194,7 @@ export function ScoredCandidateDetail({ candidate: c, jobTitle, onClose }) {
{tab === 'Job Match' && completed && (
<>
<div className="form-section-title" style={{ marginTop: 0 }}>Job-Match Score</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>Job-Match Score</h3>
<p className="text-muted text-sm mb-12">
Match this candidate against the job the CV was scored for.
</p>
@ -209,9 +209,9 @@ export function ScoredCandidateDetail({ candidate: c, jobTitle, onClose }) {
</div>
</div>
<div className="form-section-title" style={{ marginTop: 0 }}>
<h3 className="form-section-title" style={{ marginTop: 0 }}>
Matched must-have skills ({c.matchedSkills.length})
</div>
</h3>
<div className="k-tags mb-16">
{c.matchedSkills.length
? c.matchedSkills.map((s) => (
@ -220,9 +220,9 @@ export function ScoredCandidateDetail({ candidate: c, jobTitle, onClose }) {
: <span className="text-muted"></span>}
</div>
<div className="form-section-title" style={{ marginTop: 0 }}>
<h3 className="form-section-title" style={{ marginTop: 0 }}>
Missing must-have skills ({c.missingSkills.length})
</div>
</h3>
<div className="k-tags mb-16">
{c.missingSkills.length
? c.missingSkills.map((s) => (

View File

@ -203,7 +203,7 @@ function ManagerDetail({ manager: m, jobs, canSend, onClose, navigate, toast })
</div>
</div>
<div className="form-section-title" style={{ marginTop: 0 }}>Hiring Manager Portal</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>Hiring Manager Portal</h3>
<div className="grid g-2" style={{ gap: 10, marginBottom: 16 }}>
<button className="btn btn-secondary" onClick={() => go('/jobs', { openCreate: true })}>
<Icon name="plus" /> Raise Requisition
@ -219,7 +219,7 @@ function ManagerDetail({ manager: m, jobs, canSend, onClose, navigate, toast })
</button>
</div>
<div className="form-section-title">Open requisitions</div>
<h3 className="form-section-title">Open requisitions</h3>
<p className="text-muted text-sm" style={{ marginBottom: 10 }}>
Jobs are not linked to a hiring-manager id yet, so this list is the current open requisitions rather than this manager&apos;s own.
</p>

View File

@ -370,7 +370,7 @@ function OfferDetail({ offer: o, busy, onClose, onIssue, onStatus }) {
<div className="card" style={{ boxShadow: 'none', background: 'var(--bg-sunken)', marginBottom: 18 }}>
<div className="card-body">
<div className="form-section-title" style={{ marginTop: 0 }}>Compensation Package</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>Compensation Package</h3>
<div className="info-grid">
<div className="info-item">
<div className="il">Base Salary</div>

View File

@ -436,12 +436,12 @@ function RoleForm({ title, subtitle, role, bundles, bundlesLoading, busy, onClos
</label>
</div>
<div className="form-section-title">
<h3 className="form-section-title">
Permission bundles
<span className="text-muted text-sm" style={{ marginLeft: 8, fontWeight: 400 }}>
{picked.size} selected · {grantedTags.size} tags resolved
</span>
</div>
</h3>
{bundlesLoading && <p className="text-muted">Loading bundles</p>}
{!bundlesLoading && bundles.length === 0 && (

View File

@ -815,12 +815,12 @@ function Notifications({ registerSave }) {
return (
<div className="card">
<div className="card-body">
<div className="form-section-title" style={{ marginTop: 0 }}>Email Notifications</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>Email Notifications</h3>
<ToggleRow title="New applications" desc="Get notified when a candidate applies" checked={draft['notifications.email_new_applications']} onChange={(v) => setField('notifications.email_new_applications', v)} />
<ToggleRow title="Interview reminders" desc="Reminders 30 minutes before interviews" checked={draft['notifications.email_interview_reminders']} onChange={(v) => setField('notifications.email_interview_reminders', v)} />
<ToggleRow title="Offer responses" desc="When candidates accept or decline offers" checked={draft['notifications.email_offer_responses']} onChange={(v) => setField('notifications.email_offer_responses', v)} />
<ToggleRow title="Weekly digest" desc="A summary of hiring activity every Monday" checked={draft['notifications.email_weekly_digest']} onChange={(v) => setField('notifications.email_weekly_digest', v)} />
<div className="form-section-title">In-App Notifications</div>
<h3 className="form-section-title">In-App Notifications</h3>
<ToggleRow title="Mentions" desc="When a teammate @mentions you" checked={draft['notifications.inapp_mentions']} onChange={(v) => setField('notifications.inapp_mentions', v)} />
<ToggleRow title="Stage changes" desc="When a candidate moves stages" checked={draft['notifications.inapp_stage_changes']} onChange={(v) => setField('notifications.inapp_stage_changes', v)} />
<ToggleRow title="Task assignments" desc="When you are assigned a task" checked={draft['notifications.inapp_task_assignments']} onChange={(v) => setField('notifications.inapp_task_assignments', v)} />
@ -1040,7 +1040,7 @@ function Appearance() {
return (
<div className="card">
<div className="card-body">
<div className="form-section-title" style={{ marginTop: 0 }}>Theme</div>
<h3 className="form-section-title" style={{ marginTop: 0 }}>Theme</h3>
<div className="grid g-3" style={{ marginBottom: 8 }}>
<div className="card theme-opt" style={{ cursor: 'pointer', overflow: 'hidden' }} onClick={() => pick('light')}>
<div style={{ height: 80, background: '#f1f7f4', borderBottom: '1px solid var(--border)', display: 'flex' }}>

View File

@ -271,14 +271,14 @@ function TalentProfileDetail({ profileId, onClose }) {
{p.summary && (
<>
<div className="form-section-title">About</div>
<h3 className="form-section-title">About</h3>
<p className="text-muted" style={{ whiteSpace: 'pre-line' }}>{p.summary}</p>
</>
)}
{p.skills.length > 0 && (
<>
<div className="form-section-title">Skills ({p.skills.length})</div>
<h3 className="form-section-title">Skills ({p.skills.length})</h3>
<div className="k-tags" style={{ marginBottom: 16 }}>
{p.skills.map((s) => <span className="tag" key={s}>{s}</span>)}
</div>
@ -287,7 +287,7 @@ function TalentProfileDetail({ profileId, onClose }) {
{p.experience.length > 0 && (
<>
<div className="form-section-title">Experience ({p.experience.length})</div>
<h3 className="form-section-title">Experience ({p.experience.length})</h3>
{p.experience.map((e, i) => (
<div key={i} style={{ marginBottom: 14 }}>
<div className="fw-600">
@ -311,7 +311,7 @@ function TalentProfileDetail({ profileId, onClose }) {
{p.education.length > 0 && (
<>
<div className="form-section-title">Education ({p.education.length})</div>
<h3 className="form-section-title">Education ({p.education.length})</h3>
{p.education.map((e, i) => (
<div key={i} style={{ marginBottom: 12 }}>
<div className="fw-600">{e.school ?? '—'}</div>