Twenty-five components. Every screen in the network is built from them.
A component library that's small on purpose. Twenty-five pieces, every one with a clear job. If a page needs something that isn't here, the right move is almost always to compose it from what is, not to invent a new one. New components require deliberate intent and a written justification.
Buttons
Five tiers · Three sizes · Hover & disabledFive visual tiers, ranked by emphasis. Primary is the page's most important action. Secondary is a strong alt. Rust is the alert / urgent CTA ( "Get Pro listing" ). Ghost is the second option in a row. Text is the third. There is no Material-style FAB and no big rounded pill. sharp corners on purpose.
Primary button
.btn.primaryRecipe
Bluebonnet fill --bluebonnet. Paper text. 2px radius. Inter 600 at 14px. Padding 11px 18px ( regular ), 14px 22px ( lg ), 8px 13px ( sm ). Hover darkens to --bluebonnet-ink.
When to use
The single most important action on a page. Form submit. Newsletter subscribe. "Save," "Continue," "Sign in." Maximum one primary per visible region.
Secondary, ghost, & text buttons
.btn.secondary · .btn.ghost · .btn.textRecipe
Secondary: ink fill, paper text. Stronger than ghost, used when bluebonnet would conflict. Ghost: transparent fill, ink text, 1px --rule-2 border; hover flips border to bluebonnet. Text: bluebonnet color, no fill, no border; hover shifts to rust.
When to use
Secondary for "Filter," "Apply," "Export." Ghost for the second option in a row. Text for tertiary actions ( forgot password, skip, dismiss ).
Rust button & mono CTAs
.btn.rust · .btn.monoRecipe
Rust: --rust fill, paper text. Same shape as primary but rust. Mono: JetBrains Mono 700, 10.5px, 0.14em tracking, uppercase. Paper-3 fill or transparent with rust border.
When to use
Rust for urgency or alert-flavored CTAs. "Get Pro," "Apply now," "Subscribe." Mono for in-article CTAs ( "View story →," "Read the issue →" ) where the button needs to live next to mono kickers.
Search input
.search-input · the only pill in the systemRecipe
The only component in the entire system that uses border-radius: 999px. The pill shape signals "search" the same way a magnifying-glass icon does, no second affordance needed. Dark variant for the top bar's primary-band placement.
When to use
Top bar global search, in-page directory search, archive search. Never pill-shape a non-search input.
Form controls
Inter everywhere · 2px radius · States explicitEvery form field is Inter, 14px, with a 1px ink-3 border on the white card surface. Focus shifts border to bluebonnet and adds a 3px tinted halo. Errors shift to rust. The label sits above the input. never inside as a placeholder pretending to be a label.
Text input · Select · States
.form-fieldRecipe
Inter 600 11px label, then 14px input on white with 1px --rule-2 border. 2px radius. Focus: --bluebonnet border + 0 0 0 3px bluebonnet tint halo. Error: --rust border + rust helper text. Required asterisk in rust.
When to use
Every text input, email, password, URL, textarea. Same construction for the select; the chevron is an inline SVG, not a system caret.
Checkbox & toggle
.checkbox-row · .toggleRecipe
Checkbox: 18×18 box, 2px radius, rule-2 border. Checked state fills with bluebonnet, paper check glyph. Toggle: 36×20 track, fully rounded, 14px knob. Off = rule-2. On = bluebonnet.
When to use
Checkbox for "and / or" multi-choice. Toggle only for true binary on/off settings ( "Send me emails: yes / no" ). Never use a toggle for a multi-state setting.
Tags, badges & status indicators
All sharp · All mono · All earnedTags and badges are tiny mono labels with 2px corners. The point of a tag is to be readable at a glance without competing with the headline. Two-letter rules: ALL CAPS, 0.14em tracking, 9–10 px. Never round the corners off.
Tag & badge variants
.tag-x · .pro-badge · .live-dotRecipe
JetBrains Mono 700, 9.5px, 0.14em tracking, ALL CAPS. 2px radius. Color tokens determine the meaning: rust = featured/alert, bluebonnet = sponsored, gold = editorial pick, green = verified, ghost = draft / muted.
When to use
Tags label content type or status. Pro badge marks paid business listings ( inline next to the business name ). Live dot marks live events, real-time alerts, breaking-news sticky bar.
Kickers & eyebrows
The system's voice for "this is a…"A kicker lives above every headline. It identifies the desk ( "CIVIC DESK" ), the category ( "FRIDAY NIGHT LIGHTS" ), the type ( "FEATURE STORY" ), or the timing ( "BREAKING · 11:42 AM" ). Rust is the default. Variants exist for specific contexts.
Kicker variants
.kicker · .kicker--ink · .eyebrowRecipe
JetBrains Mono 700, 10.5px, 0.13em tracking, uppercase. Rust default. Bluebonnet for inverted contexts ( e.g. when rust competes ). Gold for dark surfaces only. Category colors for category tags only ( see Color chapter ).
When to use
Above every editorial headline. Always. A page without kickers reads as a generic blog. Named beats ( "FRIDAY NIGHT LIGHTS," "MEMORIAL DESK" ) are part of the editorial voice. proper-noun the recurring ones.
Cards
Six variants · One constructionAll cards share the same construction: white surface, hairline border, 3px corner radius, hover lifts the border to bluebonnet. Variants differ only by content type and decorative flair ( top-stripe for featured, date-block for events ).
News card
Image, kicker, headline, blurb, meta. The most common card on every property.
Featured card
Same as news, plus a 3px rust top stripe. Used sparingly. One per visible region.
List item
Text-only, no image. Date block, kicker, headline, byline.
Business card
Name, category, address, hours, Pro badge if applicable, action row.
Event card
Date block + body. Bluebonnet date field with gold month label.
Job card
Title, company, salary, tags. Premium variant gets a rust top stripe.
News card & featured variant
.news-card · .news-card.featuredRound Rock council clears site for new fire station off 183A
A unanimous vote ends a 14-month process. The new station opens in 2027 and serves the fast-growing corridor.
Inside the year-long fight over the Williamson County jail bond
Six commissioners, $80 million, and a vote that came down to a single seat. A first read on what just happened, and what comes next.
Five new restaurants opened in Cedar Park this week, ranked
A barbecue spot, a ramen counter, a Filipino bakery, an espresso bar, and a Tex-Mex. We ate at all five so you don't have to.
Recipe
White surface, 1px --rule border, 3px radius. Image at 16:10 with an optional photo credit chip bottom-left. Body: kicker → Fraunces 600 / opsz 36 headline → Source Serif blurb → mono meta row with bluebonnet "READ →." Hover: border shifts to bluebonnet, image scales 1.04.
When to use
The default for any list of stories. Use the featured variant for the lead story on the home page or the top of a section, max one per visible block. Photo credit chip is required when the photo isn't a stock / file photo.
List item
.list-item · text-only rowThe plumber on 1431 sold to a regional chain for $4.2M, sources say
By M. Reyes · 4 hr agoCapMetro adds a third Leander bus, citing 41% ridership growth
By J. Alvarez · YesterdayRound Rock Symphony names a new conductor. and the first season looks ambitious
By K. Doyle · YesterdayRecipe
2-column grid, 80px date column ( month mono rust 700, day 700 ), 1fr content column. Content: cat-tag kicker → Fraunces 600 14.5px headline → mono byline. Items separated by 1px --rule hairlines.
When to use
"More from the desk" sidebars. Archive pages. Search results. Anywhere a list needs to read as text-first ( no images ) and dense.
Business · Event · Job cards
.biz-card · .event-card · .job-cardRound Rock Coffee Co.
COFFEE122 Main Street, Round Rock TX 78664
OPEN · until 4 pm · 6–4 weekdays
Spring Farmers Market & Live Music on the Square
9 AM – 1 PM · Free admissionSenior Reporter, Civic Desk
WilCo Guide · Round Rock, TX $72,000 – $88,000Recipe
Business: name + category tag, address, hours with open/closed signal, Pro badge inline. Event: bluebonnet date block ( gold month, paper day ) atop body. Job: title, company, salary in money-green mono, tags row. Premium variant gets a 3px rust top stripe.
When to use
Anywhere a directory, calendar, or job board lists items. The construction stays the same; the data plugs in.
Ad slot
.ad-slot · diagonal stripe patternRecipe
repeating-linear-gradient at 45° between paper-2 and paper-3, with a 1px --rule-2 border. Mono "SPONSORED" label centered, with the slot's size beside.
When to use
Two purposes: ( 1 ) as a literal ad placeholder during layout, ( 2 ) as a visual signal that an ad will live here. The diagonal-stripe pattern is unique to ad slots. never use it for non-ad content.
Section rules
Newspaper-style horizontal rules · Four weightsRules separate sections and bands the way a newspaper does. Four weights. The thicker the rule, the more important the boundary. Never use a wash, gradient, or background tint to separate sections.
Four rule weights
.section-rule · .widget-rule · .hairline · .double-ruleSection rule · 2 px ink top border
Major section · Sits above an H2--rule top border for in-card dividers, list separators, footer columns
Recipe
Section rule: border-top: 2px solid --ink, mono section number rust 700, H2 Fraunces 600, optional right-aligned meta. Widget rule: 1.5px ink, H3 Fraunces 600 17px. Hairline: 1px --rule for everything else. Double rule: mixed 1px / 3px double pattern, used for vol-no-date rows on print and newsletter.
When to use
Section rule above every major H2 on long pages. Widget rule above each card-grid or list group. Hairline for in-card separators and footer columns. Double rule only at the top of a newsletter or print issue.
Dark bands
The only dark surfaces in the systemTwo dark blocks. The "Today in WilCo" sticky ticker, which rotates every four seconds. And the sports band, which lives below the masthead during the season. Both use bluebonnet as the fill and gold as the on-dark accent.
"Today in WilCo" sticky ticker
.dark-band · sits at top of every pageRecipe
Bluebonnet fill, 3px rust top stripe, mono gold label, Fraunces 500 ticker that rotates through 5 items every 4 seconds with a 350ms cross-fade. Always sticky at the top of the viewport, beneath the primary top bar.
When to use
Every page on every property. The rotation gives the brand a heartbeat. readers expect it. Per-property data ( weather, traffic, local index ) but always this construction.
Sports band
.sports-band · during season onlyRecipe
Bluebonnet-ink fill, 3px gold top stripe, Fraunces 700 "SPORTS" gold label, then mono game scores with gold team abbreviations + paper score numbers + mono "FINAL OT / 7TH INNING" mini-status. Right-aligned ghost mono CTA.
When to use
Football season ( Aug–Dec ), baseball ( spring ), playoffs. Hidden in the off-season. The band only appears when there are live or recent local games to report.
Quotes, stats & alerts
For emphasis, evidence, and signalPull quote
.quote-box"The math on a $80 million bond doesn't square with the population growth we're actually seeing on the ground." Comm. Bell · The lone "no" vote
Recipe
Paper-2 background, 3px rust left border, no right or bottom borders. Fraunces 400 italic at opsz 96 / SOFT 100, 22px. Attribution in rust mono, the speaker's name in rust.
When to use
One pull quote per article maximum. It exists to break the rhythm of body copy. Quotes from sources go in body paragraphs first; pull-quote treatment is reserved for the line you most want a reader to remember.
Stat block
.statRecipe
White card, rust mono label, Fraunces 700 / opsz 144 value at 40px, Source Serif description at 12.5px. Stack horizontally on desktop, vertically on mobile. Always real numbers, never round-number filler.
When to use
Lead-paragraph evidence on civic stories ( bond amounts, vote tallies, capacity numbers ). Sidebar evidence on long-form. Never invent or pad stats.
Alerts & banners
.alert.success · .error · .info · .warningRecipe
Each alert is a tinted-paper fill with a matching-hue 1px border. Round icon left, message center, close × right. Inter 13.5px copy. Bold lead sentence + supporting clause.
When to use
Success for form submissions, save confirmations. Error for blocking validation. Info for non-urgent context. Warning for almost-done flows that need an action. Never two alerts on screen at once.
Navigation
Tabs · Pagination · Breadcrumb · Author rowTabs, pagination, breadcrumb
.tabs · .pagination · .breadcrumbRecipe
Breadcrumb: mono uppercase ink-3, middle-dot separators, ink current. Tabs: mono uppercase ink-3, 2px ink underline rail, rust underline on active. Pagination: 32×32 mono squares, 2px radius, ink fill on active, rule-2 border on others.
When to use
Tabs for switching content type ( all / civic / business ). Pagination for archives, search results, directory. Breadcrumb on any sub-page deeper than two clicks.
Avatar & author row
.avatar · .author-rowRecipe
Round avatars with Fraunces 700 initials, sized 24 / 32 / 48. Bluebonnet and rust fills are the only two photo-fallback colors. Chips are 2px-radius pills ( exception ) for filter / tag inputs.
When to use
Avatar in bylines, comment threads, internal CMS. Chip for filter selections ( location, category, status ), never for content category tags.
Newsletter signup
The most-used module on every propertyLives at the bottom of every article, on the homepage above the fold, and as a sticky bottom drawer on mobile. The construction below is the only newsletter signup block in the system. variants change copy only, never the layout.
Inline newsletter signup
.nl-signupFive things you need to know about Williamson County before the weekend.
Filed Friday morning. No fluff. No ads. About four-minute read.
Recipe
White card with 3px rust top stripe. 2-column grid: copy left ( kicker → headline → blurb ), form right ( label + email + full-width primary button + mono helper ). On mobile, stacks to one column.
When to use
End of every article. Homepage above the fold ( hero-adjacent ). Top of the "Subscribe" page. Sticky mobile bottom drawer for first-time visitors.
Iconography
Lucide · 16×16 stroked · No fillsAll icons are lucide. 16×16 default size, stroke-width: 2, rounded line caps, no fills. The system uses about fifteen icons across every property. If a screen needs one that isn't here, pull it from lucide before inventing one.
The icon set
15 icons · Lucide-react in productionRecipe
Pull from lucide.dev. 16×16 default, 20px in nav bars, 24px in big-button contexts. stroke="currentColor" so the icon picks up the text color. Never fill. Never use a different icon library inside the same screen.
When to use
Icons accompany text labels in nav, in form helpers, in empty states. Icon-only buttons require a tooltip. Decorative-only icons get dropped. every icon in the system earns its keep by clarifying an action.
Do & don't
Component-system hygieneWhat keeps the system clean
- Compose, don't inventIf a page needs a new thing, build it from existing pieces first. Most "new" needs are an old component with different copy.
- Use 2px and 3px corners2px for tags, badges, buttons, inputs. 3px for cards. Nothing else.
- Hover = border-color shiftEvery card and ghost button signals interactivity by shifting border from rule-2 to bluebonnet. Never by changing fill.
- One primary button per regionIf two CTAs are competing, one of them is wrong.
- Mono for stamped labels, Inter for chromeIf it's stamped, dated, or categorized, mono. If it's tap-able UI, Inter.
- Kicker above every editorial headlineNo exceptions on news cards, list items, hero blocks. Anonymous content loses the brand.
- Photo credit on every photoMono uppercase chip bottom-left. "PHOTO BY M. REYES." File photos labeled "FILE PHOTO."
What breaks the component system
- Don't round corners offThe brand is allergic to pills. The only pill in the system is the search input. that's it.
- Don't use shadows for elevationThe brand uses border-color hover, not shadow lift. Glowing or large drop shadows feel SaaS, not editorial.
- Don't gradient buttonsSolid fills only. Bluebonnet doesn't become teal at the bottom.
- Don't put icons inside primary buttons by defaultPrimary buttons are usually text-only. An arrow → at the end is fine. Lucide icon left of the label is not.
- Don't reach for sans for a headlineIf you need big bold sans, you actually need Fraunces 700. the system has no display sans.
- Don't pile two alerts on screen at onceOne signal at a time. If two competing messages are needed, the design is wrong.
- Don't theme cards per categoryThe category color lives in the kicker only. never on the card surface, never as a left border, never as a top stripe.
The next chapters compound on this one
After components are locked- Social templates. Instagram feed + carousels, TikTok / Reels covers, LinkedIn, Facebook, stories. The locked grid + locked type cadence built from this chapter, photography doing the variety.
- Newsletter template. One master template mapped to the six type tone treatments. Uses cards, kickers, dark bands, and pull-quotes from this chapter.
- Photography direction. Mood, palette, rules, examples of what we do and don't shoot.
- Sub-brand toolkit. One-pager: how to spin up Cedar Park Scoop, WilCo Families, or a new vertical without breaking the system.