Skills comparison
One person against the team average.
Built with
Skills review
H1 2026 cycle · manager ratings, 1 to 5
Person
Ayesha rates furthest above the team average on leadership and furthest below it on technical depth.
View data as a table
| Category | Ayesha | Design average |
|---|---|---|
| Leadership | 4.2 | 3.5 |
| Communication | 4.6 | 4 |
| Delivery | 3.8 | 3.7 |
| Technical depth | 3.4 | 3.9 |
| Collaboration | 4.8 | 4.1 |
| Ownership | 4.4 | 3.8 |
skills-comparison.tsx
"use client";
import { useState } from "react";
import { LegendItem, RadarChart, Select, SelectOption } from "@nexera-ui/react";
// Sample data
interface Person {
id: string;
name: string;
initials: string;
role: string;
team: string;
email: string;
location: string;
status: "Active" | "On leave" | "Remote" | "Invited";
}
const PEOPLE: Person[] = [
{
id: "p1",
name: "Ayesha Khan",
initials: "AK",
role: "Senior product designer",
team: "Design",
email: "ayesha.khan@northwind.io",
location: "London",
status: "Active",
},
{
id: "p2",
name: "Sara Malik",
initials: "SM",
role: "Payroll specialist",
team: "People Ops",
email: "sara.malik@northwind.io",
location: "Lisbon",
status: "On leave",
},
{
id: "p3",
name: "Ahmed Raza",
initials: "AR",
role: "Engineering manager",
team: "Engineering",
email: "ahmed.raza@northwind.io",
location: "Lahore",
status: "Remote",
},
{
id: "p4",
name: "Mia Jensen",
initials: "MJ",
role: "Product manager",
team: "Product",
email: "mia.jensen@northwind.io",
location: "Copenhagen",
status: "Active",
},
{
id: "p5",
name: "Luis Ortega",
initials: "LO",
role: "Frontend engineer",
team: "Engineering",
email: "luis.ortega@northwind.io",
location: "Madrid",
status: "Active",
},
{
id: "p6",
name: "Yuki Tanaka",
initials: "YT",
role: "Recruiter",
team: "People Ops",
email: "yuki.tanaka@northwind.io",
location: "Tokyo",
status: "Active",
},
{
id: "p7",
name: "Chloe Martin",
initials: "CM",
role: "Data analyst",
team: "Finance",
email: "chloe.martin@northwind.io",
location: "Paris",
status: "Remote",
},
{
id: "p8",
name: "Omar Haddad",
initials: "OH",
role: "Support lead",
team: "Customer",
email: "omar.haddad@northwind.io",
location: "Dubai",
status: "Invited",
},
];
const SKILLS = [
"Leadership",
"Communication",
"Delivery",
"Technical depth",
"Collaboration",
"Ownership",
];
// Manager ratings from the H1 2026 review cycle, 1 to 5.
const TEAM_AVERAGE: Record<string, number[]> = {
Design: [3.5, 4.0, 3.7, 3.9, 4.1, 3.8],
Engineering: [3.4, 3.6, 3.9, 4.1, 3.8, 3.7],
};
const RATINGS: Record<string, number[]> = {
p1: [4.2, 4.6, 3.8, 3.4, 4.8, 4.4],
p3: [4.6, 4.2, 4.0, 3.9, 4.3, 4.5],
p5: [3.1, 3.5, 4.4, 4.6, 3.9, 4.0],
};
const REVIEWED = PEOPLE.filter((person) => person.id in RATINGS);
/** Skills comparison: one person's review ratings against their team average, with their strengths and growth areas. */
export default function SkillsComparison() {
const [personId, setPersonId] = useState<string | number | null>("p1");
const [hidden, setHidden] = useState<readonly string[]>([]);
const person = REVIEWED.find((p) => p.id === personId) ?? REVIEWED[0];
if (!person) return null;
const mine = RATINGS[person.id] ?? [];
const team = TEAM_AVERAGE[person.team] ?? [];
const firstName = person.name.split(" ")[0] ?? person.name;
const series = [
{ name: firstName, data: mine, styleIndex: 0 },
{ name: `${person.team} average`, data: team, styleIndex: 7 },
];
const gaps = SKILLS.map((skill, i) => ({ skill, gap: (mine[i] ?? 0) - (team[i] ?? 0) })).sort(
(a, b) => b.gap - a.gap,
);
const signed = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 1,
minimumFractionDigits: 1,
signDisplay: "exceptZero",
});
const toggle = (name: string, visible: boolean) => {
setHidden((current) => (visible ? current.filter((n) => n !== name) : [...current, name]));
};
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<div className="mx-auto grid max-w-5xl gap-6">
<header className="flex flex-wrap items-end justify-between gap-4">
<div className="grid gap-1">
<h2 className="text-heading-h2 text-primary">Skills review</h2>
<p className="text-body-default text-secondary">
H1 2026 cycle · manager ratings, 1 to 5
</p>
</div>
<Select
label="Person"
value={person.id}
onChange={(key) => {
setPersonId(key);
setHidden([]);
}}
className="w-full @md:w-64"
>
{REVIEWED.map((p) => (
<SelectOption key={p.id} id={p.id} label={p.name} description={p.role} />
))}
</Select>
</header>
<div className="grid gap-4 @3xl:grid-cols-[3fr_2fr]">
<RadarChart
title={`${person.name} against the ${person.team} team`}
description={person.role}
summary={`${firstName} rates furthest above the team average on ${gaps[0]?.skill.toLowerCase()} and furthest below it on ${gaps[gaps.length - 1]?.skill.toLowerCase()}.`}
categories={SKILLS}
series={series}
max={5}
hiddenSeries={hidden}
onHiddenSeriesChange={setHidden}
isLegendHidden
/>
<aside className="grid content-start gap-5 rounded-lg border border-default bg-surface p-4 @3xl:p-6">
<section aria-labelledby="skills-series" className="grid gap-2">
<h3 id="skills-series" className="text-heading-h4 text-primary">
Show
</h3>
<ul className="grid gap-1">
{series.map((s) => (
<li key={s.name}>
<LegendItem
label={s.name}
seriesIndex={s.styleIndex}
shape="line"
hasMarker
isSelected={!hidden.includes(s.name)}
isDisabled={hidden.length === 0 ? false : !hidden.includes(s.name)}
onChange={(visible) => {
toggle(s.name, visible);
}}
/>
</li>
))}
</ul>
</section>
{[
{ title: "Strengths", items: gaps.slice(0, 2) },
{ title: "Growth areas", items: gaps.slice(-2).reverse() },
].map((group) => (
<section key={group.title} className="grid gap-2">
<h3 className="text-heading-h4 text-primary">{group.title}</h3>
<ul className="grid gap-1">
{group.items.map((item) => (
<li
key={item.skill}
className="flex justify-between gap-3 text-body-default text-secondary"
>
{item.skill}
<span className="text-body-strong text-primary tabular-nums">
{signed.format(item.gap)} vs team
</span>
</li>
))}
</ul>
</section>
))}
</aside>
</div>
</div>
</div>
);
}
More dashboards and analytics blocks
- KPI overviewFour headline metrics with trend and change.
- Sales dashboardRevenue over time against target.
- People analyticsHeadcount by team, type and tenure.
- Recruitment funnelApplicants at each stage and the drop-off between.
- Engagement survey resultseNPS score and check-in patterns across the week.
- Budget varianceHow the budget moved from plan to actual.
- Live status boardReal-time counts with tiny trends.
- Chart statesLoading, empty and error versions of any chart.