Skip to content

feat: playground#2084

Open
Adebesin-Cell wants to merge 59 commits into
amannn:mainfrom
Adebesin-Cell:docs/refactor-playground
Open

feat: playground#2084
Adebesin-Cell wants to merge 59 commits into
amannn:mainfrom
Adebesin-Cell:docs/refactor-playground

Conversation

@Adebesin-Cell

@Adebesin-Cell Adebesin-Cell commented Nov 8, 2025

Copy link
Copy Markdown

Closes #1623.

Reboot of the playground refactor with the v1 scope agreed in #1623 (comment): a single category (Translations) with two detail pages (Server / Client Components), MDX-driven content via Code Hike, locale-prefixed routing, and the labeled-boundary visual identity from the Vercel app-router playground applied to next-intl's design system.

What's in this PR

  • New playground/ workspace (Next 15.5.15, React 19, Tailwind v4, shadcn primitives)
  • next-intl middleware + [locale] routing with en / de messages
  • @next/mdx + Code Hike pipeline (remarkCodeHike + recmaCodeHike) wired in next.config.ts, with a custom <Code> RSC and 6 annotation handlers (mark, callout, focus, link, fold, line-numbers)
  • Sidebar / header / locale-switcher / theme-toggle chrome adapted from the design system spec — left-aligned brand, icon-prefixed section labels, quiet active-state accent, bg-background corner labels punching through hairline borders
  • Two detail pages backed by content.mdx + a sibling live demo component:
    • Translations / Server components — async demo using getTranslations + setRequestLocale
    • Translations / Client componentsuseTranslations with an interactive ICU {name} interpolation
  • Each page wraps content in <PlaygroundBoundary label="Demo"> + a separate <PlaygroundBoundary label="Output" variant="dotgrid"> so the rendered demo reads like a render preview
  • Empty future categories (Formatting, Routing, Patterns) are reserved in the sidebar with a dimmed coming soon placeholder so adding them later is just a data change in src/lib/nav.ts
  • Polished byline footer with Lucide-iconed source/docs/inspiration links
  • Smoke Playwright tests covering landing + both detail pages in both locales
  • Existing examples/example-app-router-playground is untouched — e2e coverage stays where it lives until the new playground absorbs it in a follow-up

Screenshots

Landing — dark 01-landing-en-dark
Landing — light 02-landing-en-light
Client Components — dark 05-client-en-dark
Client Components — light 06-client-en-light

Out of scope (explicit)

  • Other categories (Formatting, Routing, Patterns) and their pages
  • An intl-explorer-style interactive formatting playground
  • Code Hike tabs / CodeWithNotes (no v1 page needs them)
  • Migrating Playwright e2e off examples/example-app-router-playground

Try it

pnpm install
pnpm --filter playground dev

Open http://localhost:3000 — middleware redirects to /en, then click into either Translations page. Locale switcher (top-right) flips to /de, theme toggle persists.

Notes for review

  • Turbopack is disabled for the playground because @next/mdx's loader options aren't serializable for the current Turbopack pipeline (pnpm dev/build use webpack)
  • Two as any casts in next.config.ts work around a peer-resolved Next 14 / 15 NextConfig type clash from @next/mdx
  • A future iteration can hand-write per-page README.md files for GitHub-side browsing; currently the README slot just points at content.mdx

@vercel

vercel Bot commented Nov 8, 2025

Copy link
Copy Markdown

@Adebesin-Cell is attempting to deploy a commit to the next-intl Team on Vercel.

A member of the Team first needs to authorize it.

@Adebesin-Cell Adebesin-Cell changed the base branch from docs/refactor-playground to main November 8, 2025 20:06
@amannn

amannn commented Nov 10, 2025

Copy link
Copy Markdown
Owner

Nice, let me know once it's ready to have a look!

@amannn

amannn commented Nov 10, 2025

Copy link
Copy Markdown
Owner

Oh, just one thing after having a quick look: The patterns example I've set up was intended to be the playground. You can just move all your code there! Let's call it "patterns" for now since there is already example-app-router-playground.

@Adebesin-Cell

Copy link
Copy Markdown
Author

Hey @amannn 👋 sorry for the long radio silence on #2084, but I'm back on it. I just pushed a full reboot.

What’s there now (matches the v1 scope you sketched in issue #1623 #1623 (comment)):

  • Translations category with Server Components + Client Components pages
  • Locale-prefixed routing (/[locale]/...) with next-intl/middleware, en + de
  • MDX content authored as content.mdx per page, rendered via Code Hike (remarkCodeHike + recmaCodeHike) with annotation handlers (mark, callout, focus, link, fold, line-numbers)
  • Vercel-app-router-playground visual identity adapted to your design system: hairline borders with mono corner labels, sidebar nav with Lucide section icons + quiet active-state accent
  • Output preview boxes use a subtle dot-grid backdrop, so the rendered greeting reads like a preview
  • Lucide icons throughout, blue-300 logo on dark / blue-700 on light
  • Existing examples/example-app-router-playground is untouched — Playwright coverage stays where it lives

Screenshots attached: landing (en/de, light/dark), both detail pages in both locales, mobile.

Ideas for next iteration (would love your read before scoping):

  1. Formatting category — small intl playground (date + number for v1), live preview + copyable next-intl config
  2. Routing category — mixed routing example (like https://next-intl.dev/examples#app-router-mixed-routing), showing locale-prefixed vs non-prefixed boundary
  3. Patterns category — locale switcher, Server Actions, metadata, error files (each as its own page)
  4. Auto-generate per-page README.md from content.mdx to avoid drift
  5. Eventually move Playwright suite into the playground so it doubles as docs + E2E

No rush at all, happy to split any of these into separate PRs once v1 feels good 🙏

@Adebesin-Cell Adebesin-Cell force-pushed the docs/refactor-playground branch from 54efbec to b28d1e5 Compare May 8, 2026 13:05
@Adebesin-Cell Adebesin-Cell marked this pull request as ready for review May 8, 2026 13:18
@amannn

amannn commented May 12, 2026

Copy link
Copy Markdown
Owner

Awesome, thanks for your hard work here @Adebesin-Cell! I'll make a note of this and will get back to you!

@Adebesin-Cell

Adebesin-Cell commented May 13, 2026

Copy link
Copy Markdown
Author

Hi @amannn,

I picked up the work from comment, was curious how those ideas would shape up. Attached a video so you can see how it looks.

Also did a quick a11y pass:

  • aria-pressed on the chips
  • aria-live on the Explorer Output so screen readers announce format changes
  • proper `aria-labels' on the icon-only buttons
  • aria-current on the active locale

Had fun doing this 😅

I’m also thinking of an accessibility/preferences panel similar to how Intl Explorer Playground does theirs, toggles for reduced motion, muting the Explorer’s screen-reader announcements, accent color, etc

I have some ideas and would love to include them in the playground as well.

Screen.Recording.2026-05-13.at.07.17.56.1.mov

@amannn

amannn commented May 13, 2026

Copy link
Copy Markdown
Owner

Oh wow @Adebesin-Cell, that's awesome! 😮🔥

I'll get back to you with some more detailed feedback!

@amannn

amannn commented May 22, 2026

Copy link
Copy Markdown
Owner

Hey @Adebesin-Cell, sorry, I still haven't found the time to dig more into this.

But two things:

  1. feat: playground #2084 (comment)
  2. Can you split the PR, so there's an initial one with only the translations section with pages Server Components and then Client Components?

It'd be good to make sure the foundation and direction is solid first. Then, we can look into adding more pages.

But the integration of the Intl explorer is a really cool idea, I think we should add this along with the formatter pages in follow-up PRs!

@Adebesin-Cell

Copy link
Copy Markdown
Author

No worries @amannn 🙏 Did the split — this PR is now scoped to just the Translations foundation.

Moved everything else into a Draft follow-up: #2337. It stacks on the same base and contains:

  • Formatting → Dates / Numbers / Explorer (the useFormatter integration covering all five methods + named formats registry + Code Hike snippet)
  • Routing → Mixed routing
  • Patterns → Locale switcher, Browser language, Cache components placeholder
  • Settings dialog (reduce motion, announce output, accent palette) — persisted to localStorage
  • Localized /not-found with a small useFormatter/useTranslations mini-playground
  • Root-layout refactor so the 404 renders correctly
  • Accessibility pass (aria-pressed, aria-live, aria-current, etc.)

@amannn amannn left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks really cool, thanks @Adebesin-Cell! 🙌

Here's a first review pass, hopefully there's something useful in this for you. Some remarks are questions to you, let me know what you think!

Comment thread playground/src/app/layout.tsx Outdated
Comment thread playground/src/i18n/request.ts Outdated
import { hasLocale } from 'next-intl';
import { routing } from './routing';

export default getRequestConfig(async ({ requestLocale }) => {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next.js 16.3 is on the horizon and is expected to ship with next/root-params. We should adjust for this once the release is out (related to #1632 and #2327)

Comment thread playground/public/file.svg Outdated
Comment thread playground/src/assets/logo.tsx Outdated
Comment thread playground/tests/playground.spec.ts Outdated
Comment thread playground/src/app/globals.css Outdated
Comment thread playground/src/components/playground/byline.tsx Outdated
Comment thread playground/src/app/[locale]/page.tsx Outdated
Comment thread playground/src/components/ui/dropdown-menu.tsx Outdated
Comment thread playground/src/components/playground/byline.tsx Outdated
…review feedback

Relocate the playground into the `example-app-router-patterns` example amannn
set up, and scope this PR down to the Translations foundation (Server + Client
Components) per review. Addresses the inline feedback on amannn#2084:

Structure & config
- Move everything from top-level `playground/` into
  `examples/example-app-router-patterns/`, keeping the existing `/design`
  reference page (middleware now skips `/design`)
- Root `layout.tsx` owns `<html>`/`<body>` + Inter font + providers; the
  `ThemeProvider` is rendered inline (dropped the `ClientProviders` wrapper)
- Adopt the example's conventions: Inter (not Geist), `clsx` (not `cn`),
  prettier (single quotes, no bracket spacing)
- Drop shadcn/ui + `components.json` + `cn`/`tailwind-merge`; the three
  consumers (sidebar, theme toggle, locale switcher) are now hand-rolled
- Remove Playwright tests/config (e2e lives in `/e2e` on main now) and the
  unused Next template SVGs

Styling
- globals.css: palette lives directly in `@theme` as `--color-*`; dropped the
  chart vars and the wholesale palette re-mapping. Only the semantic tokens
  that flip under `.dark` remain (for runtime theming via next-themes)
- Marker + dropdown no longer use the accent color; accent used sparingly
- Fix theme-switch flash via `disableTransitionOnChange`

Translations pages
- Move page headings + nav labels into messages so they're translatable;
  `lib/nav` now holds message keys instead of hardcoded strings
- Single-column MDX (the two-column layout cut off code)
- Snippets use `useTranslations` (works in non-async Server Components too),
  no `setRequestLocale`/`params` noise; server example matches the snippet
- Trim Code Hike to the one annotation actually used (`mark`); remove unused
  annotation handlers, `demo-card`, `two-column`, per-page READMEs

Logo & byline
- Logo matches the docs (currentColor + padded viewBox, no clipping)
- Byline drops the hardcoded credit + uppercase label, keeps Source + Docs

@amannn amannn left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice work @Adebesin-Cell! 👏 Thanks also for the helpful replies to my comments!

I've added a few more inline comments, let me know what you think!

strokeLinecap="square"
/>
</g>
</svg>

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like there's still something off with this dot at the bottom right:

Image

Docs:

Image


export function GitHubLink({path}: {path: string}) {
// Encode segments so dynamic ones like `[locale]` work as a GitHub URL
const href = `https://github.com/amannn/next-intl/tree/main/${path

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move the base url to a config file at src/config.tsx? This is also used for byline.tsx.

}

/* Subtle dot-grid backdrop for the "output" boundary box */
.dotgrid {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from #2084 (comment)

Yep, I'd prefer keeping as much styles out of global.css as possible and move to component code. Otherwise this could end up as dead code during a refactor.


export default async function ClientComponentsPage({params}: Props) {
const {locale} = await params;
setRequestLocale(locale);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from #2084 (comment): Ok to use requestLocale for now, but I'd already avoid setRequestLocale and just use dynamic rendering for now. Next.js 16.3 with next/root-params was expected for last week already, I think this should be out any minute now.

const locale = await getLocale();

return (
<html lang={locale} suppressHydrationWarning className={inter.variable}>

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be part of [locale]/layout.tsx. There's no need for a layout at app/, right?

import {useLocale} from 'next-intl';
import {useParams} from 'next/navigation';

export function LocaleSwitcher() {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be broken:

Area.mp4

return (
<div
role="group"
aria-label="Switch language"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are still hardcoded inline labels around, we should switch all consistently to t(…)

{t('tagline')}
</p>
</div>
<PlaygroundBoundary label={t('examples')} className="space-y-10">

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it t('examples')? Maybe a generic "pages" could fit better terminology-wise? (also the rendered labels)

<Link
href={item.slug}
key={item.slug}
className="group bg-background hover:bg-muted flex flex-col gap-1.5 px-5 py-4 transition-colors"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hover:bg-muted: I think that's too dark, can we use a lighter shade?


:root {
--background: var(--color-gray-50);
--foreground: var(--color-gray-900);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two approaches to dark mode:

  1. Using semantic tokens that switch their internal value (used here)
  2. Use dark: class names to declare which specific values a given element has

I know the first one is tempting from an engineering perspective, but it creates a quite heavy abstraction IMO that doesn't allow for much fine-tuning on a case-by-case basis and also a lot of upfront declarations (e.g. specific ones like --sidebar-foreground below).

If you compare what e.g. the Next.js playground does:

bg-gray-900 hover:bg-gray-800

It takes a bit of discipline to apply colors consistently, but I think it's just dead-simple. Reuse of colors is typically achieved through component reuse then (e.g. by using <Prose> where relevant).

I think there was a tweet from Adam from the Tailwind team at some point that he also prefers this approach for the reasons listed here.

Can we switch to this simpler inline approach to remove another architectural layer and reduce complexity?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor playground to be more useful

2 participants