Flow with it.
One system, every component.
The classic skin: Inter typography, ink-black 2px borders and the periwinkle
accent, expressed as CSS custom properties in
design-system.css.
The resume and the blog are both built from these parts.
Specimen
One card, re-inked and re-shaped by whatever the two columns say.
Set — Classic / Periwinkle
Core
Identity details
The accent line and kicker reproduce the rotated section labels of the slideshow;
the notched label chip comes from the “Hello” tab on the about page.
Core
Buttons & chips
The solid button wipes to grey with a pop, like the original btn-black; the
accent is the primary/brand button and hovers the same way — light wipe, ink
text; the ghost outline skew-fills ink, mirroring salv-down-cv-btn.
Core
Tabs
One strip, three modes (_Tabs +
.ds-tabs). The geometry is
shared — the 2px rule, the accent tint and rule marking the selected tab (the rail's
own current-row grammar turned horizontal), square corners because a radius meeting the
rule leaves a notch, a scrolling strip with edge fades when the tabs outgrow the column
— and the mode decides the element and the ARIA.
--nav is anchors over real
routes with aria-current
(the group strip above navigates this very showcase);
--panel ships as anchors and
is promoted to the full tablist pattern by
tabs.js once its panels are
confirmed present; --display
is a closed segmented radiogroup for choices about the reader's eyes rather than the
content — the blog's Cards/List switch. One tab stop per strip: focus roves with the
arrow keys, and selection is manual where it is expensive (a navigation) and automatic
where it is free (a class toggle).
Panel selects between siblings in one document. Without JS these anchors still work as fragment links; with it, a screen reader hears "tab, selected, 2 of 3" and the panel is the very next stop.
Display selects between two renderings of the same content, which is why it is not a tablist at all — see the segmented control below.
Core
Page hero & statline
Every listing page opens the same way: kicker, an oversized black title, a light
lead and the mono .ds-statline
counters whose pulsing dot marks the live count. Pages compose the pieces per
surface — a breadcrumb above, chips or search below — so the hero is
a pattern, not a partial.
Blog
Notes on cloud & data architecture, .NET engineering and the practice of shipping software, written from the field, with the code to back it up.
- 4 topics
- 3 series
- 18 articles
Core
Breadcrumb
The trail under a page hero (.ds-breadcrumb):
periwinkle separators, hover-accent links and an
aria-current ending that never
links to itself.
Core
Badges & pills
The status vocabulary in one place: notched sheet labels, the “coming
soon” pills, the draft-preview chip, date pills, the colour-coded role
pills from the project cards and the tech chips. Grey consistently means
“not yet”; periwinkle means live, featured or in focus.
Core
Empty state
The quiet placeholder (_EmptyState)
when a topic or a filtered archive has nothing to show: accent line, a muted
message, an optional hint and an optional onward link.
Try a different search or filter.
Clear search and filters
Core
Drawer
An edge-anchored overlay panel
(.ds-drawer) that slides in
over a scrim, right by default and
--left on request, in three
widths. Square corners — it is a surface, and the 2px edge frames it. The markup is
one <aside> plus a
trigger; the dialog role, focus trap, Escape, scrim and scroll lock come from the
behaviour layer. Its rows are generic on purpose: the navigator behind the tab on the
right of this page is the same component that will hold saved-for-later articles.
Core
Modal & dialog
The drawer's sibling, re-anchored to the middle
(.ds-modal): the same head,
scrolling body and optional foot, over a denser scrim, in four widths. Square corners
like every surface, but it carries the pop shadow — unlike the drawer it floats
free of an edge and needs the lift. Markup is one
<div> plus a trigger;
role, focus trap, Escape, scrim and scroll lock come from the behaviour layer.
The --reader variant is
the point of it: a whole article — hero, prose, taxonomy, author sign-off —
composed from the same classes the post page uses, with reading progress under the head.
Articles are meant to open here rather than redirect, with the full page as the fallback;
that wiring is still ahead, so this is the reference build.
Modal
Default width, 640px.
The panel traps Tab while it is open and hands focus back to the button that opened it when it closes. Escape and the scrim both close it. The body scrolls on its own — overscroll-behavior: contain keeps the page underneath still.
Focus lands on the panel rather than the close button, so a screen reader announces the title before it offers you the way out. A panel can override that with data-ds-modal-autofocus.
Discard this draft?
--sm is 420px: one question, two answers.
A confirm keeps its body to the consequence and puts the choice in the foot, destructive action last and never pre-focused.
Designing with tokens, shipping with partials
How one CSS file and a handful of Razor partials keep the resume and the blog looking like the same site.
A design system on a one-person site earns its keep the day the resume and the blog stop drifting apart. Two stylesheets become one; two ideas of what a card is become one partial. The interesting part is not the tokens — it is what you have to give up to keep them true.
Tokens are a contract, not a palette
A token is only worth declaring if something is forbidden from stepping
around it. --ds-accent is not “the purple”; it is the
single place the purple is allowed to come from. The moment a component
hard-codes #7371FC, the token has become documentation.
If a value appears twice and can change once, it is a token. If it appears twice and must change twice, it was never a system.
That rule is what makes the radius tiering hold: corners are bound to a component's role, not its measured size, so a full-width button is still a control and stays rounded while the card behind it stays square.
- Surfaces — cards, dialogs, drawers — take square corners
- Controls take 6px, whatever their width
- Info tokens and avatars take the full pill
Mock the data, not the markup
The showcase renders the production partial against a sample view model. A copied snapshot of the markup rots quietly; a partial fed real-shaped data breaks loudly, which is the behaviour you want from a reference page.
The panel you are reading this in is the same argument applied to a component: it is a modal holding the actual article view, so the day the post page changes, this changes with it.
Core
Toast
The third overlay, and the only one nobody opens
(.ds-toast): it arrives on
its own and leaves on its own, so there is no panel on the page and no id to point at
— the host is built on first use and a caller supplies only the message.
The primary skin is the fuse: a bordered card at the bottom right whose accent
underline drains left-to-right as the dwell runs out, the signature wipe run in
reverse as a countdown, paused by hover or focus along with the timer behind it.
The secondary is the dock chip
(--dock) — the dock's
position and skin, a mono kind-tag and an action word, springing up from the bottom
centre with the one overshoot the system allows. Cards carry a payload; chips carry a
verb. One at a time per host, always dismissible,
role="alert" for failures
and role="status" for
everything else, entrance and exit frozen under reduced motion — the fuse is not,
because it is a readout rather than decoration.
CV download started
KUMAR_CHANDRACHOODA_Resume.pdf · 412 KB
Preview mode is on — drafts are visible
<button data-ds-toast="CV download started"
data-ds-toast-msg="KUMAR_CHANDRACHOODA_Resume.pdf · 412 KB"
data-ds-toast-kind="success">Download</button>
dsToast({ title: 'Filter cleared', variant: 'dock', tag: 'archive',
action: 'undo', onAction: restore });
| Option | Default | What it does |
|---|---|---|
variant | fuse | dock for the chip. |
kind | info | success, warning, danger — recolours the fuse and the tag, and switches the live region from polite to assertive. |
dwell | 3500 / 6000 | ms on screen; 0 stays until dismissed, and drops the fuse with it. |
action | — | The verb. onAction runs, then it dismisses unless the handler returns false. |
tag | the kind, on a chip | The mono kind-tag. |