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

September 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 says "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: tailwind.config.ts + 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, grid lines only

surface-sunk #FAFAFA, listing card hover

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 and card titles

Inter: body text, labels, and controls

Barlow: goal card descriptions


Scale in use

40pxPage title
28pxSection heading
21pxGoal card title
20pxListing card title
15pxDetail page prose
13pxCard description
10px boldEyebrow label

The homepage headline sizes itself against its own column with clamp(36px, 13cqw, 72px), 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; grid cards draw a 1px accent ring inside their edge


The gap grid. Every card grid draws its lines from borders, never from a gap showing a background through:

// wrapper
grid border-l border-t border-divider
// each child
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/[0.06] active:bg-accent/[0.12] transition-colors duration-150 focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-accent

Listing card

Source: src/components/ListingCard.tsx · The only card the guide uses. Its footer 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.

All levels

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.

Studio page parts

Source: src/app/listings/[id]/page.tsx, OutboundStudioLink.tsx

Discipline tags

West AfricanSenegalese

Beginner callout · only when beginnerFriendly === true

New to this?

This studio welcomes beginners. No prior experience required.

Visit website · the accent-filled conversion action

Visit website

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

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.

List, filters active

"No studios match your current filters." + Clear all 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

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 ten dated sections. 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-friendly48 confirmed yes, 1 confirmed no, 0 unconfirmed
With a named cultural root11

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
  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 default card and the "New to this?" callout on the studio page. false and null both show the level instead. "Beginners welcome" always means confirmed.

Cultural roots feed search, not the page

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.

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 rooted10
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');

// [id]/page.tsx: up to 3, same category and a shared goal, then same category
primary = same category && shares a beginnerPersonaTag;
return [...primary, ...sameCategoryFiller].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.

Trade-offs

Cobalt is cooler and less distinctive than the brand orange.

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 cards share borders in a grid. Scaling one inward would expose the container behind it on all four edges.

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 · September 2026