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.

PaletteAccentOn white
Civic (active)#1A56C46.62:1
Gallery#3E4A4F9.14:1
Studio#8E3B2F7.46:1
Clay#E8581F3.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

44 to 76pxStudio name
40pxPage title
32pxTicket area
28pxSection heading
26px italicBeginner statement
24pxSign value
22pxTicket name
21pxGoal card title
17pxStudio page prose
15pxTicket description
10px boldEyebrow label

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:

// studio tickets
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

Social dance classes where you can learn, laugh, and connect with others.

Hover · accent at 6%

01

I want to dance and meet people

Social dance classes where you can learn, laugh, and connect with others.

Press · accent at 12%

01

I want to dance and meet people

Social dance classes where you can learn, laugh, and connect with others.

Keyboard focus · inset ring

01

I want to dance and meet people

Social dance classes where you can learn, laugh, and connect with others.

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.

Beginners welcome

Studio Arts

DC

Winter Palace Studio

Georgetown and Bethesda studio offering regular figure-drawing and portrait sessions with live models, open-format, by appointment.

Intermediate

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

West AfricanSenegalese

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 website

Once 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.

Search by studio, style, or neighborhood (salsa, ceramics, Dupont)
All areas

10 studios

Clear filters

Empty 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."

Newsletter 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.

Send me the next one

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.

9:41 dmvartsguide.com

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.

StateWhat the person seesWhat they can do
DefaultSix cards under an accent top edgeChoose a goal
HoverAccent background at 6%Choose a goal
PressAccent background at 12%Choose a goal
Keyboard focusA 1px accent ring inside the card's edgeChoose a goal
Phone widthOne columnChoose a goal
Reduced motionThe same steps, instantlyChoose a goal
No goals configuredThe section does not renderBrowse 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 studios49
Category31 studio arts, 18 dance
Area21 DC, 18 Maryland, 10 Northern Virginia
Beginner-friendly47 confirmed yes, 2 confirmed no, 0 unconfirmed
With a named cultural root10

Listing, the fields that shape the experience

type Listing = {
  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

// FilteredListings.tsx: an exact tag match, combined with every other filter
if (persona && !l.beginnerPersonaTags.includes(persona)) return false;
GoalPublic studios
I want to dance and meet people5
I want a creative hobby31
I want something culturally rooted9
I want to move and reduce stress7
I want to train seriously21
I just want to try something19

Visible listings and similar studios

// listings.ts: every public query starts here
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.

DMV Arts Guide: Living IXD Spec

Built by Articulate Digital · October 2026