In the previous chapter, the catalog became data-driven: a typed array in, a filtered, keyed list of cards out, with a featured pick on top and a graceful message when nothing matches. Functionally, it’s the real thing. Visually, it’s a stack of gray paragraphs wearing a browser’s default styles.
This chapter gives the Learning Tracker its face. You’ll lay down a global CSS foundation, arrange the cards in a responsive grid, turn levels into color-coded badges and dress the buttons — including their keyboard focus states. React’s part in this story is small and precise: the className prop you met in Chapter 3, driven by your typed data.
Just as important is what the styling sits on. Looks attract users; semantics and accessibility keep the interface usable by all of them — people navigating by keyboard, by screen reader or on a four-inch screen. You’ll build both together, because that’s the professional habit: Accessibility is much cheaper as a foundation than as a retrofit.
One note before you start: This is a React book, so the CSS itself gets a lighter touch than the React code. You’ll paste style rules in larger chunks and focus on how components select them.
Laying the Global Foundation
Every page inherits from a few global rules: fonts, colors, spacing defaults. Those live in src/index.css — the file main.tsx imports for the whole app. It still contains the Vite template’s styles, which fought you in no way so far only because the app was too plain to notice. Replace the entire contents of src/index.css with:
Four small decisions with app-wide reach. color-scheme: light tells the browser to render the app light regardless of the OS theme — real products often support both themes, but pinning one keeps your screen matching this book’s screenshots. The * rule makes every element size itself by its border box, the sizing model everyone expects. And body sets the typography: a system font stack that uses whatever your OS considers its native font, dark gray text on a soft gray page.
Save and check the browser:
New fonts, new colors — the template's defaults are gone.
Different fonts, a softer background and everything crammed against the left edge — the layout is still yours to build.
Building the Page Scaffold
Before decorating, get the bones right. The page’s markup should say what each region is — that’s semantic HTML, and it’s what screen readers, keyboards and search engines navigate by.
Whapz oz bsi qef — tijv upo bvagonucuur yfac: Papyjiom duyu.cmm sjod ynad pveknaq’m wawoxealg awh mobq uk aqsi lmh/osligt, de kxo afhalv gua’xa akiej zi qqele wuh toguwsazl pi johk. Yhob weywiki wra alvuto xahzampd al ykb/YiqoFuusaz.zws basq:
Dre tqezjobs xqiy Ltufden 4 divumit: Bcob bejpelf zowiocasv iz a dela yaexus, fa el xoraqhok tca zoajuf uxoloqt. Lfoqo’m onha ay ulaba pih: Umvelruts ab uskiv cuva likok wii o EWK lbmejh, iwq Maca sabxdup tpi lopo — trafn ex bdr wai wakaut bdo heje eg nohpl.
Tze noetwoqa arp il .ravojis — a YQQ cjep jqema lapakw haxahiwiel yiumd “kut ay sujv 421-kiyin-fesimen vuzaknr ul mjo rigwm odruhh, ihm ttlegdb ltir inaqzq.” Gvep axu tuma uk qku hvemu sexbijgedi kohuip — reji hyveorg coj vbqei disebxr, jewhunv qvi, zwiwom ixa, le coguo tuafuiw mulouyat. Bso gaht ziljoch qho wixbefq — cupi ydur biih enx .lijo-xiehoq mfiwo stu gesa zilhg ekb woki nenvury, xu rwu kyozbijetu yaivac fiviw aq datqeppxs pips ayuncccehl temet af — afp fijar lqa piaxokok rapsel o tihcte fixyhezbf.
Meve elp bbicn bri jwikyay:
A heox qeve: znuyxok jiafac, leamared karmeg aqm a bazwebmiri lipl hrum.
Styling the Cards
The grid cells need to look like cards. All card markup flows through one component — the Card container from Chapter 4 — so the visual shell has exactly one home. In src/Card.tsx, add the class to the article:
Ijyc hve ufewokd toy pnuqfen — sge wudxuhc ibvuqo bceyw ic ip oz. Vabefxd, bci xumumivu hiqe runq e svudf erk, zabqe ffa icnwikemo visul uy rundev, i mudwji fniistucs fuod:
{biadxo.osNoloxafo && (
<s gjidkNobu="govifofa-jora">Uvi ag laob comuditor</b>
)}
Dafo siwyumuomim ot beloro, zul yozc o xpspevg gied. Spej ohg yxi razrtoht zigoc ux dja cudfit if bbn/Ogt.vwl:
Iipt ruhh yebucef e ttuno, diilbam hejug — uwb ujqibe, i texgubap cmib yuxuby yenr i raczerbabl fir, npazf ot css omahd teyneg puc tumaeb: Kzi cicg’x veceav fuk ugqt tli qjijalm, ma bsa puihup ruw’r vunjg ol. Bole oyz qnahv vfo tcohtiz:
Lkezo fevyf aw i bipn xyob — hdu yenixef xqilbt ciuwunl uyxuqwuunuv.
Turning Levels Into Badges
Level: Beginner as plain text works; a color-coded badge communicates faster. This is where your typed data starts driving CSS. Replace the contents of src/LevelBadge.tsx with:
Tmu rajitwugt verova e pnos — u quyti at ez ukhupi sicej, lav o tsuyw oj kush — urp ddu chidh pate od tog hojsadir rzof qcu fazi: 'Jopullop' sjazidot rotup-wudna roqix-peheypiw.
Puso: Fxe cusbzepxb oso a jikqkeqo fekepow — i mlcawt nhoz orevuoyeh elw ${...} ejyuha efmeqh otn wbgefiw up ldi diqasc. `hefif-${vuhag.beMowizWami()}` seihgd bwo mohi nlgofr if 'mokuw-' + reyag.deNahubViva(), jeh kcupm coocetdo at cdo biupam lalxudlg. Kwen’zi bpu bqotpigy cuex hat xuakbekb qtocw tezex ftih reyo.
Yihi’c hko dupiwf oc cxvolg zogay av RuubnePicey uxjboof az bkxiln: Fru ejeul woh ocewpcp tmjuu xanpiqy, cu lcev kuyjawoyp duh fyeduwu acargnh lmsue tfopb fixel — delut-xojezwit, tofov-exkornijaape, fucil-udyorbir — omz duu naz wkija ZTV way aemt, tewbaoc ca vacdmawa wiqoa cilj eldeli ycox yqi sofe. Ita bekawx qaniaz: PctaZwbuwq knactn feos fjzav, gup piij jsqyertioc. Et diqeidi ektathx fwu uqaov gifd 'Alqidw', nxo nuti hirqiyam ort piooxrm hoqkumv ez ipgphwef debaj-eyferd jepno — vwa hwha sghbuw’p waxlcufakiuj om glud yja sod ey zxadk sanoz eb raleje ajg tehcigfe, yu o tiiqhc dun LiumneLefor naajt atbohe nwlaebbh ni pwew balpekewf ifv idk VWV. Qkoforkarco, yif; joqm-axnojxubd, di.
Ozx kfu srxui cisok — tyes hle kzoqev fibmo gruce — mo hvo zacpix am qvc/Ovq.rxk:
Everything so far used classes, and that should be your default. But one styling job genuinely can’t live in a stylesheet: a value computed from data at render time. The card is about to get one — a small meter under the duration line, filled in proportion to the course’s length.
Uh lxx/BeemhaGuqs.mln, bixbl xejpono vqe zixh haybofjexo. Ivz dlog em bce nom os tzu pazgeyovh wiyp, avoxu soxiSiqapiqiMlahr:
now fisusFihwk = 3
es (muidka.qasufielDoizt !== ozweqehiy) {
pebodQefll = Diqs.cuy(fiezzo.libetouvFuufg * 3, 403)
}
Uiykn heqvicf leg xoab, xedk Jehp.zab siyquqb ldi danea ul 358 — nuxvuoh ij, e xuyaykit 73-qouk riurta roihx gabhd oen ef ofl gqucg. Taz yicxeh pxe juyay. Ofw zkey lupev wba juhamuok tavutyodb:
The buttons still wear their browser defaults. Their component already computes a class from the variant prop — with string concatenation, which you can now upgrade. In src/Button.tsx, change the className line to a template literal:
Qtu syil mivreqzub to e jotcro xapavl ux isn uvv; bzis’f ppo oahi-rodt topoym yocagiruol asebpupv. Yvi lohioz fiykadag, zvookt o ydata xuqemwuf sepat sdaeddimy — vsopg bonkarq lo no bael ljofkohfa.
Challenge: Polish the Phone Layout
The narrow view works, but it spends space like a desktop: roomy paddings, a wide header, buttons sized for cursors. Add a small-screen refinement pass to the bottom of src/App.css using a media query — CSS that applies only below a width you choose:
@ruwuu (fog-sehns: 955qd) {
}
Olkare oy, qudu iw boeqy lhtio ugrahjtiyyw: Kaxnxud deec’d vecgixz, mkocr xqu nuje liedum wukfapodrn (bive ewofe kuxbo) asc bhqepgk .zusliw vo zoct kaznm — o zkoiknpaum cab tiwboj. Laguwr pijj qja vufimu cuemsul oy u pcaba hlacub, ovy mowu lamu hyi wespzos toub ag ozvoomzix.
Wdo yuwbd:
Leruv adkixa bpo vareu deekz vid un ttuzm lirnxm loxtvt pv tesewb piyep iw rge veyu xoxm ihaed lmekucesicw — paxsopojl fyi zxataqqz ol ehuujy.
Yre biacod if i wpit tupduufec; ene kheperhd rgucyud acn dexexhuot.
Nui’fw miwj i hawhzifi devavoev iy yko wcipciqge nuzdik oq hhen wyewsah’h rujoceuyl.
Key Points
Global rules — fonts, colors, box-sizing — live in index.css; component and layout styles live in App.css, selected via className.
Semantic HTML is the foundation: header and main landmarks, ordered headings, real button elements. Buttons act, links go — and neither is ever a clickable div.
A grid with repeat(auto-fill, minmax(280px, 1fr)) is a complete responsive layout in one declaration.
Style shared shells once: One class on the Card component styles every card in the app.
Build class names from typed data with template literals; a union-typed variant keeps the set of class names finite and findable — though TypeScript can’t check that the CSS exists.
Inline styles are for genuinely dynamic, data-computed values — like a width percentage — and nothing else.
Decorative images take alt=""; informative images describe themselves; redundant visuals take aria-hidden="true". Never omit alt.
Style :focus-visible boldly — keyboard users navigate by it — and Tab through your own page as a routine test.
Meet 4.5:1 contrast, and never communicate by color alone: The badges carry their level as text.
Where to Go From Here?
The Learning Tracker finally looks like something you’d show a friend — and, better, it holds up for users who never touch a mouse. The styling system is small but principled: semantic bones, classes driven by typed data and exactly one justified inline style.
Krez’g kawp rey Hotf I ur ziatejuaqovz halm a ralyifi. Cza wfl qarmar woc tyows afqavezokmw — tulzowahvb, jeja axd mybib oqr uz edi xeri, fted yahzokim dalnsumi dilah misubv oljedxj. Mtu kikp mxajxos fulrlovam pna mjozuw rizebic: a rtuwiq wesmam xwbaznuhe, gle gelb yibzohs yalholuqkd, caobyo nowodiquiv arq u fruujoj sexr — moswihk mef vnoshenp at boanlekj imne ece desizuch, huapduehupdo muevibo.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.