defuss-shadcn / Website / Resume & Portfolio / skill-matrix
Skill MatrixBLK
What someone can do, in groups - each skill a name, its level as a native meter and a note; the levels as bars or as five dots, or the skills as tags without levels. Part of the Resume & Portfolio blocks.
On this page (5)
§Bars
Each level a native meter named by its label - the value and the range come with the element. The rows of a group share their columns (subgrid); the groups fill an auto-fill grid.
§Dots
data-variant='dots': each level five dots - a mask turns the meter's fill into whole dots, so the meters need max='5' and whole values. Spoken languages with their CEFR level as the note.
§Stars
data-variant='stars': each level five stars in amber, like the Rating component - a star tile masks the meter, so a value of 4.5 shows four and a half stars. The meters need max='5'; the text inside says the value for browsers without meter support.
§Tags
data-variant='tags': no levels - the skills as pills with their note inside. The names are spans, there are no meters.
§CSS view file
/* -- Skill Matrix (website · Resume & Portfolio) -------------------------- What someone can do, in groups: each skill a name, its level as a native <meter> and a note (years, a CEFR level). Levels as bars (default) or as five dots (dots - the mask turns the meter's fill into whole dots), or no levels at all (tags, pills). The rows of a group share their columns through subgrid; the groups fill an auto-fill grid - no breakpoints, no script. */@layer components { /* every box in the block measures border-box - the same on any page */ :where(.mk-skill-matrix, .mk-skill-matrix *, .mk-skill-matrix ::before, .mk-skill-matrix ::after) { box-sizing: border-box; } .mk-skill-matrix { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr)); gap: 2rem 2.5rem; width: 100%; } .mk-skill-matrix-group { display: grid; align-content: start; gap: 0.875rem; min-width: 0; } .mk-skill-matrix-title { display: flex; align-items: center; gap: 0.5rem; margin: 0; font-size: 0.875rem; font-weight: 600; & svg { flex: none; width: 1rem; height: 1rem; color: var(--muted-foreground); } } /* name | level | note - one set of columns for the whole group */ .mk-skill-matrix-list { display: grid; grid-template-columns: minmax(0, 1fr) minmax(4rem, 6.5rem) auto; align-items: center; gap: 0.625rem 0.875rem; margin: 0; padding: 0; list-style: none; } .mk-skill-matrix-skill { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; } .mk-skill-matrix-name { grid-column: 1; font-size: 0.875rem; font-weight: 500; overflow-wrap: anywhere; } .mk-skill-matrix-note { grid-column: 3; color: var(--muted-foreground); font-size: 0.75rem; font-variant-numeric: tabular-nums; text-align: end; white-space: nowrap; } /* the level: a native <meter>, drawn as a bar - track and fill per engine */ .mk-skill-matrix-level { --mk-skill-matrix-bar: 0.375rem; display: block; grid-column: 2; width: 100%; height: var(--mk-skill-matrix-bar); border: 0; border-radius: 999px; background: var(--muted); appearance: none; /* Chromium resolves a percentage height of its bar to half the meter: a length, inherited from the meter */ &::-webkit-meter-bar { height: var(--mk-skill-matrix-bar); border: 0; border-radius: 999px; background: var(--muted); } &::-webkit-meter-optimum-value, &::-webkit-meter-suboptimum-value, &::-webkit-meter-even-less-good-value { height: var(--mk-skill-matrix-bar); border-radius: 999px; background: var(--primary); } &::-moz-meter-bar { border-radius: 999px; background: var(--primary); } } /* dots: five dots - a 20% tile per dot, so a fill that ends on a whole step ends between two dots */ .mk-skill-matrix[data-variant="dots"] { & .mk-skill-matrix-list { grid-template-columns: minmax(0, 1fr) 5rem auto; } & .mk-skill-matrix-level { --mk-skill-matrix-bar: 0.75rem; border-radius: 0; mask: radial-gradient(circle closest-side, #000 92%, transparent) 0 50% / 20% 100% repeat-x; /* square ends: a rounded end would bite into the last whole dot */ &::-webkit-meter-bar, &::-webkit-meter-optimum-value, &::-webkit-meter-suboptimum-value, &::-webkit-meter-even-less-good-value { border-radius: 0; } &::-moz-meter-bar { border-radius: 0; } } } /* stars: each level five stars - a star tile masks the meter, so half steps show half a star; amber like Rating */ .mk-skill-matrix[data-variant="stars"] { --mk-skill-matrix-star: #f59e0b; --mk-skill-matrix-empty: color-mix(in oklch, var(--foreground) 14%, transparent); & .mk-skill-matrix-list { grid-template-columns: minmax(0, 1fr) 6.25rem auto; } & .mk-skill-matrix-level { --mk-skill-matrix-bar: 1.25rem; border-radius: 0; background: var(--mk-skill-matrix-empty); mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01z'/%3E%3C/svg%3E") 0 50% / 20% 100% repeat-x; &::-webkit-meter-bar { border-radius: 0; background: var(--mk-skill-matrix-empty); } &::-webkit-meter-optimum-value, &::-webkit-meter-suboptimum-value, &::-webkit-meter-even-less-good-value { border-radius: 0; background: var(--mk-skill-matrix-star); } &::-moz-meter-bar { border-radius: 0; background: var(--mk-skill-matrix-star); } } } /* tags: no levels - the skills as pills, the note inside */ .mk-skill-matrix[data-variant="tags"] { & .mk-skill-matrix-list { display: flex; flex-wrap: wrap; gap: 0.5rem; } & .mk-skill-matrix-skill { display: flex; align-items: baseline; gap: 0.375rem; padding: 0.3125rem 0.75rem; border: 1px solid var(--border); border-radius: 999px; } & .mk-skill-matrix-name { font-size: 0.8125rem; } & .mk-skill-matrix-level { display: none; } } /* -- Print ------------------------------------------------------ */ @media print { .mk-skill-matrix-group { break-inside: avoid; } /* a level is a background - paper keeps it only when asked */ .mk-skill-matrix-level { print-color-adjust: exact; } } /* -- Accessibility ------------------------------------------- */ @media (prefers-contrast: more) { .mk-skill-matrix-note, .mk-skill-matrix-title svg { color: var(--foreground); } .mk-skill-matrix-level { outline: 1px solid var(--foreground); } .mk-skill-matrix[data-variant="tags"] .mk-skill-matrix-skill { border-color: var(--foreground); } } @media (forced-colors: active) { .mk-skill-matrix[data-variant="stars"] .mk-skill-matrix-level { outline: 0; background: GrayText; &::-webkit-meter-bar { background: GrayText; } &::-webkit-meter-optimum-value, &::-webkit-meter-suboptimum-value, &::-webkit-meter-even-less-good-value { background: Highlight; } &::-moz-meter-bar { background: Highlight; } } .mk-skill-matrix-level { forced-color-adjust: none; background: Canvas; outline: 1px solid CanvasText; &::-webkit-meter-bar { background: Canvas; } &::-webkit-meter-optimum-value, &::-webkit-meter-suboptimum-value, &::-webkit-meter-even-less-good-value { background: Highlight; } &::-moz-meter-bar { background: Highlight; } } .mk-skill-matrix[data-variant="tags"] .mk-skill-matrix-skill { border-color: CanvasText; } }}Comments, ideas or improvements? Edit this page's source on GitHub