DMV Arts Guide
Living Interaction Design Specification
DMV Arts Guide helps adults of color in the DMV find art and dance classes. The hardest part of starting a class is often walking into an unfamiliar room and wondering whether you belong. The guide is built to make that step easier.
Platform
Web
Stack
Next.js + Tailwind
Data
Static JSON, 49 studios
Updated
October 2026
System Specification
This living specification documents the interaction patterns, design constraints, and system decisions that govern how the product behaves.
It is the reference for design tokens, component patterns, and interaction rules used across the product.
Product Philosophy
The thinking behind the design decisions.
The core insight
A search bar assumes you already know what you want. Most people starting an art or dance class do not, and what they want to know is whether a studio is a place for them. So the guide leads with what someone is trying to do, and it names each studio's traditions the way the studio names them.
Design principles
Name the tradition
Generic labels hide the thing a person is looking for. The editorial rules ban "world dance," "ethnic dance," and "African dance" when the specific form is known, and ask for the form by name: West African, Bharatanatyam, Soca, Hand Dance.
Roots are claimed, never assigned
A cultural root is recorded only when a studio or its instructors claim it. Identity tags such as "Centers Black joy and community" and "LGBTQ+ affirming" follow the same rule and come only from a studio's own public materials.
Goals before search
The homepage opens on six goals rather than a search box. Search is still there for people who already know what they are looking for.
Only what can be confirmed
Every claim in a listing has to trace to the studio's website, class pages, registration page, instructor bios, or named press. A field nobody can confirm stays empty, and a studio is marked beginner-friendly only when that has been confirmed.
A context layer, not a directory
The guide sits just before someone signs up for a class. It is not a marketplace, a booking platform, or an events calendar, and it does not reduce a studio to location, time, category, and price.
The six goals
Rendered with the site's own classes and token values from HomepagePersonaCard.tsx. This is not a screenshot.
Start here
What are you looking for?
Language Standard
Canonical terms and phrasing, from docs/UX_PATTERNS.md and the editorial rules that govern every listing.
Studios to visitors, listings in the code
Internally the record is a listing: the route is /listings and the type is Listing. To a visitor it is always a studio. The nav says Studios, the results count says "10 studios," and the empty state names only what was applied: "No studios match your search," "No studios match your search and filters," or "No studios match your current filters."
Name the form
- West African, and the specific form (Senegalese, Ghanaian) when it is known
- Bharatanatyam, Kuchipudi, Odissi
- Caribbean dancehall, Soca, Afrobeat
- Merengue, Cumbia, On1 and On2 salsa
- Hand Dance
- Afro-diasporic
Never flatten it
"World dance""Ethnic dance""Global styles""Cultural movement""Traditional dance"without naming the tradition"African dance"when the specific form is known
Comparatives
Avoid
largest, best, top, only, one of the few, one of the biggest, most acclaimed, most established
Use instead
long-running, established, a major, one of the larger, not commonly found at regional studios
Claims nobody can check
Banned unless a named source supports them:
"highly regarded," "award-winning," "beloved by the community," "a DC institution," "known for its welcoming teachers," "one of the city's hidden gems"
Register
A listing reads like a museum program catalog or a city cultural guide: factual, neutral, concise, and culturally respectful. It does not read like a brochure, a Yelp review, a press release, or a travel blog. Sentences aim at 20 to 25 words, and there are no em dashes.
Also banned as filler: "welcoming environment," "creative journey," "vibrant community," "safe space," "supportive atmosphere," "passion for the craft."
Design Tokens
Every token rendered as a live specimen. Source of truth: src/app/globals.css (@theme) + src/lib/palette.ts. Contrast ratios are measured on white.
Colors
Accent and brand
accent #1A56C4
Focus and primary action, 6.62:1
accent-hover #1544a0
Hover on filled accent, 8.87:1
brand-accent #E8581F
Brand color: logo rule and hero underline
Neutrals
ink #111111, 18.88:1, headings and titles
charcoal #3F4A5A, 8.98:1, body copy and chips
eyebrow #595959, 7.00:1, eyebrow labels
body #666666, 5.74:1, card descriptions
muted #7A6F5D, 4.93:1, nav links and meta text
divider #EBEBEB, rules, ticket edges, and the goal grid's lines
surface-sunk #F4F6F9, the cool band behind a studio page's panels
One switch, four palettes
ACTIVE_PALETTE = 'civic' injects one palette onto the page. Brand orange is the same in all four; only the accent changes.
| Palette | Accent | On white |
|---|---|---|
| Civic (active) | #1A56C4 | 6.62:1 |
| Gallery | #3E4A4F | 9.14:1 |
| Studio | #8E3B2F | 7.46:1 |
| Clay | #E8581F | 3.59:1 |
Typography
Families
Barlow Condensed: headings, card titles, and signs
Barlow: body text, labels, and controls
Newsreader italic: two lines, and only two
Scale in use
The homepage headline sizes itself against its own column with clamp(36px, 13cqw, 72px) in Barlow Condensed 800, so the accent phrase keeps one underline per line at 320, 375, 768, and 1600.
Shape, spacing, and focus
Corners and shadows
Sharp edges, no shadows. Rounded shapes are reserved for bullet dots and the map's own internals.
Page width
max-w-5xl mx-auto px-6
Touch targets
44px minimum on every control
Focus
A 2px accent outline by default. Goal cards draw a 1px ring inside their edge; studio tickets a 2px ring just outside theirs
Card grids never paint behind an empty cell. Studio tickets sit apart in a gapped grid with a transparent container, so a partly filled row shows the page. The goal grid draws its shared lines from borders instead:
grid gap-4
// the goal grid, then each goal card
grid border-l border-divider border-t-2 border-t-accent
h-full border-r border-b border-divider bg-white
Motion
Cards
Background color only, 150ms. A card never scales on press.
Buttons
Visit website and the List toggle scale to 0.96 on press.
Map to list
A studio chosen on the map switches to the list, scrolls to its card, and highlights it for 1200ms.
Reduced motion
Every duration drops to 0.01ms.
Component Library
Live specimens rendered with the site's classes and token values.
Goal card
Source: src/components/HomepagePersonaCard.tsx · Every state is a background step, so each can be told apart from the others at a glance.
Default
01
I want to dance and meet people
Hover · accent at 6%
01
I want to dance and meet people
Press · accent at 12%
01
I want to dance and meet people
Keyboard focus · inset ring
01
I want to dance and meet people
min-h-[160px] border-r border-b border-divider bg-white px-7 py-7 hover:bg-accent/6 active:bg-accent/12 transition-colors duration-150 focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-accent
Studio ticket
Source: src/components/ListingCard.tsx · The only card the guide uses, everywhere a studio appears. A slip with the brand orange along its top, the area first, and a tear line above the footer, which depends on showBestFor and on whether a studio is confirmed beginner-friendly.
Dance
DC
Salsa With Silvia
Beginner salsa and bachata taught in a social, partner-based setting, no experience needed.
Studio Arts
DC
Winter Palace Studio
Georgetown and Bethesda studio offering regular figure-drawing and portrait sessions with live models, open-format, by appointment.
Dance
DC
Baila DC
A structured 6-week beginner series for On2 salsa, progressive levels with partner rotation built in.
Good for
Adults new to salsa who want a structured, progressive course that leads directly into local Latin social dance events.
Left: confirmed beginner-friendly. Middle: confirmed not, so the footer shows the level. Right: the homepage variant with showBestFor, shown in its hover state, accent at 6%. Press deepens it to 12%; a ticket never scales.
bg-white border border-divider border-t-[6px] border-t-brand-accent hover:bg-accent/6 active:bg-accent/12
Studio page parts
Source: src/app/listings/[id]/page.tsx, OutboundStudioLink.tsx
Discipline tags
Signs · where it is and how to get in
- Location
- Takoma ParkMaryland
- Level
- All levels
- How to book
- Sign in and pay at class
Metro and price join the row only when a studio has them.
Beginner callout · only when a studio is beginner-friendly and verified
New to this?
This studio welcomes beginners. No prior experience required.
Details panel · while a studio's schedule and prices are being confirmed
Details
We're still confirming this studio's schedule and prices. Check with them before you go.
Visit websiteOnce confirmed, the line gives way to "Details can change. Check the studio's website for current offerings." bg-accent text-white hover:bg-accent-hover active:scale-[0.96] · opens in a new tab
Search, view, and area
Source: src/components/FilteredListings.tsx, FilterBar.tsx · When a goal is active, only the area filter shows. Search also maps a beginner's words to the guide's own names, so "pottery" finds studios listed as Ceramics or Wheel Throwing, and "dance" finds every dance studio.
10 studios
Clear filtersEmpty state
Source: src/components/EmptyState.tsx · The ink-outline button is the recovery tier. The specimen shows filters alone; after a search, a coverage line sits under the message and the button reads "Clear search."
No studios match your current filters. Try removing one to see more options.
Clear all filtersNewsletter signup
Source: src/components/EmailSignup.tsx · Posts to Formspree.
Monthly
What's new in the DMV arts scene
New studios, seasonal classes, and finds worth knowing about across DC, Maryland, and Northern Virginia. One email per month.
Unsubscribe anytime.
Success · role="status"
You're on the list. See you next month.
Error · role="alert"
We couldn't add you to the list. Please try again, or email hello@articulatedigital.co and we'll add you.
While sending: the button disables and reads "Sending," so a second click cannot send a second request.
Interactive Prototype
A clickable phone frame using the site's classes and all 49 public studios, in the order the site shows them. Choose a goal, open a studio, then follow a similar studio from there.
Home
Interaction Patterns
Recurring patterns across the site, from docs/UX_PATTERNS.md and docs/state-matrices.md.
Goal card states
The card's states are planned in a state matrix. tests/homepage-primary-action.spec.ts asserts that rest, hover, and press are three different backgrounds.
| State | What the person sees | What they can do |
|---|---|---|
| Default | Six cards under an accent top edge | Choose a goal |
| Hover | Accent background at 6% | Choose a goal |
| Press | Accent background at 12% | Choose a goal |
| Keyboard focus | A 1px accent ring inside the card's edge | Choose a goal |
| Phone width | One column | Choose a goal |
| Reduced motion | The same steps, instantly | Choose a goal |
| No goals configured | The section does not render | Browse studios |
No overlays
No modals, dialogs, or drawers anywhere on the site. The one exception is the map's popup, which is part of the map library.
Empty and no results
One state in different containers, told apart by what caused it rather than by which view is open. Inside the results, recovery calls clearAllFilters() in place, so every studio returns without a page load. The copy and every clear control name only what was applied: search, filters, or both. After a search, the list adds one line naming what the guide covers; the map shows the headline alone.
List, filters active
"No studios match your current filters." + Clear all filters
List, search only
"No studios match your search." + "The guide lists beginner-friendly studios for dance, pottery, painting and drawing, photography, glass, printmaking and sculpture." + Clear search
List, search and filters
"No studios match your search and filters." + the same coverage line + Clear search and filters
List, area filter only
"No studios found in this area." + Clear area filter
Map, studios without a location
"This studio isn't on the map yet."
Map tiles fail three times
"The map isn't loading right now." with a path back to List
A studio page says what is still being confirmed
Until a studio's schedule and prices are confirmed, its Details panel says so and asks the visitor to check with the studio before going. Once they are confirmed, the line gives way to the general reminder that details can change.
Errors show a reference, not a message
A render error page shows the error's digest. Production builds redact the message, and the digest is the part that is both safe to show and useful for tracing.
Where a studio link returns to
A card on a results page carries the results URL. The studio page's back link then reads "Back to studios" and returns there; opened any other way, it reads "Browse all studios."
Planned before built
Changes are planned with a state matrix: what a person sees in each state, what they can still do, how they recover, and which component renders it. docs/state-matrices.md holds one dated section per feature. States that cannot occur are named rather than skipped: the site is fully public, so there is no permission state.
Data Architecture
Studio data is one JSON file built into the site, so no list or studio page waits on a request for it. There are no accounts.
The catalog, counted from the data
| Public studios | 49 |
| Category | 31 studio arts, 18 dance |
| Area | 21 DC, 18 Maryland, 10 Northern Virginia |
| Beginner-friendly | 47 confirmed yes, 2 confirmed no, 0 unconfirmed |
| With a named cultural root | 10 |
Listing, the fields that shape the experience
id: string; name: string;
category: 'dance' | 'studio-arts';
disciplines: string[];
area: 'DC' | 'Maryland' | 'Northern Virginia';
culturalRoots: string; // cultural lineage of the practice; empty string if not applicable
environmentTags: string[]; // only from public studio descriptions
verificationStatus: 'verified' | 'partially-verified' | 'needs-verification';
beginnerFriendly: boolean | null; // true = yes, false = no, null = not yet confirmed
beginnerPersonaTags: string[]; // powers homepage intent matching
cardSummary?: string; // plain, not promotional
bestFor: string[];
studioImages?: string[];
visibility?: 'hidden'; // suppress from all public queries without deleting
}
Excerpt from src/types/listing.ts. Comments are the file's own.
Three beginner states, two displays
true shows "Beginners welcome" on the ticket and in the studio page's Level sign, the same words in both places. false and null both show the level instead. The "New to this?" callout adds a full sentence only where the studio is also verified.
Cultural roots are matched, never shown
culturalRoots is matched when someone searches, so a search for a tradition finds the studios rooted in it. The chips on a studio page come from disciplines. They also rank a studio page's "You might also like," after a shared discipline.
Verification decides two things
verified keeps the general reminder that details can change. partially-verified and needs-verification show the line saying the schedule and prices are still being confirmed. And the "New to this?" callout, which makes the most confident claim on the page, appears only on verified studios.
How a goal matches studios
if (persona && !l.beginnerPersonaTags.includes(persona)) return false;
| Goal | Public studios |
|---|---|
| I want to dance and meet people | 5 |
| I want a creative hobby | 31 |
| I want something culturally rooted | 9 |
| I want to move and reduce stress | 7 |
| I want to train seriously | 21 |
| I just want to try something | 19 |
Visible listings and similar studios
const publicListings = listings.filter((l) => l.visibility !== 'hidden');
// similar.ts: up to 3 from the same category, by shared discipline family, then shared cultural roots, then same area
candidates = same category, not this studio;
return candidates.sort(byDiscipline || byRoots || byArea || byFileOrder).slice(0, 3);
Decision Log
Product decisions with rationale. The "why" behind the product.
Goals are the primary navigation
Context
Most people starting a class do not know what to search for.
Decision
Six goal cards are the homepage's primary navigation, marked by the accent edge along the top of the grid. Search sits below them.
Rationale
The person self-selects an intent, then views or searches from there.
Trade-offs
Someone who already knows a studio's name takes one more step to reach it.
Cobalt is the accent, orange is the brand color
Context
The guide needs a recognizable brand color and an interaction color that reads at every size.
Decision
The Civic palette makes cobalt #1A56C4 the accent for focus and the primary action, and keeps orange #E8581F as the brand color.
Rationale
Measured on white, cobalt is 6.62:1, which clears the 4.5:1 text threshold at any size. A blue interaction color also keeps the orange distinct, so the brand color marks the brand rather than everything on the page.
Trade-offs
Cobalt is cooler and less distinctive than the brand orange.
Studios are tickets, not rows
Context
Cells in a shared grid read as a directory, and the guide is a recommendation layer.
Decision
Each studio is a ticket: a slip with the brand orange along its top, the area first, the name, a short summary, and a tear line above who it is good for or its level. Tickets sit apart in a gapped grid.
Rationale
A ticket reads as one place someone could go, and the area answers the first practical question before the name.
Trade-offs
Tickets are taller than grid rows, so lists run longer on a phone.
A studio page leads with where and how
Context
Someone deciding whether to go needs the practical answers before the description.
Decision
The name and the logistics sit on a white top as plain signs: location, level, how to book, and Metro when there is a station. The studio's own words and the Details panel sit on a cool band below.
Rationale
The signs answer whether a studio is reachable and right for someone's level at a glance; the band holds the reading.
Trade-offs
Long booking instructions wrap across several lines in a sign.
Serif only where the guide speaks
Context
The guide's reassurance should read as said to the visitor, while everything else stays plain and scannable.
Decision
Barlow and Barlow Condensed set everything, with Newsreader italic in two places: the beginner statement on a studio page and "Every listing personally reviewed." on the homepage.
Rationale
A second face used rarely draws the eye to the two lines a hesitant beginner most needs to read.
Trade-offs
A second family adds a font file to every page load.
Say what is still being confirmed
Context
A visitor plans a first class from a studio page, and schedules and prices change.
Decision
Until a studio's schedule and prices are confirmed, its Details panel says so and asks the visitor to check with the studio before going, and the page states that beginners are welcome as a plain attribute rather than as a promise. The "No prior experience required" callout appears once a studio is verified.
Rationale
Telling someone what to check costs less trust than letting them arrive to find a class has changed.
Trade-offs
The page is plainer about what it does not yet know than a directory would be.
Press is shown by background, never by scale
Context
On a phone there is no hover, and opening a goal loads a new page, so a tap needs a visible response.
Decision
Pressing a goal card deepens its background to 12% accent.
Rationale
The goal cards share borders in a grid, so scaling one inward would expose the container behind it on all four edges. Studio tickets follow the same rule so every card answers a press the same way.
Trade-offs
A color step is less tactile than a shrink, so the three backgrounds are tested to stay distinct.
No paid placement
Context
The guide states it takes no paid submissions, so any badge on a studio has to be unmistakably editorial.
Decision
Studio pages carry no placement badge, and the site has no paid placement. The featured field drives only the editorial Editor's Picks.
Rationale
A reader choosing a studio can trust that no listing was paid for.
Trade-offs
The guide gives up placement as a source of revenue.
Only the studio's own photo
Context
A visitor reads a photo on a studio page as a picture of that studio.
Decision
A studio page shows a photo only when it is the studio's own. Otherwise the page uses its no-photo layout.
Rationale
A photo that is not the studio would misdescribe it, on a guide whose value is describing studios accurately.
Trade-offs
Studio pages are less visual than a directory filled with stock imagery.
How listings are made
Context
A visitor decides whether to walk into a studio based on its listing.
Decision
Each listing moves through structured intake from the studio's own materials, a draft checked against that source, and a review in which every draft is read, usually edited, and approved.
Rationale
Nothing reaches the page without being checked against the studio's own materials and reviewed.
Trade-offs
Adding a studio takes longer than publishing submissions as written.