Tailwind Plus screens
StableFive screens in the Tailwind Plus style: a centred hero, a sidebar app shell, a sign-in form, a header and a loading screen.
Built with plain Tailwind utilities in the shape of Tailwind Plus’s application and marketing blocks, branded with the RChursin_ mark. In light mode the sidebar is near-black on a white page; in dark mode the layout turns into the Catalyst style, with the content on a raised panel. Hero and Navbar take no props.
Tailwind Plus hero
import { TailwindPlusHero } from '@rchursin/screens'
/** * A centred hero under a simple header, in the Tailwind Plus style. The screen has no <main> * of its own, so the page wraps it in one. Bracketed copy is yours to replace. */export default function TailwindPlusHeroPage() { return ( <main className="min-h-dvh bg-white dark:bg-neutral-950"> <TailwindPlusHero /> </main> )}Usage
import { TailwindPlusAppShell, TailwindPlusHero, TailwindPlusLoading, TailwindPlusNavbar, TailwindPlusSignIn,} from '@rchursin/screens'<TailwindPlusAppShell> {/* the shell draws the page's <h1> ("Home"); start below it with <h2> */} <h2 className="text-base font-semibold">Recent runs</h2></TailwindPlusAppShell>Examples
Tailwind Plus app shell
The sidebar layout with a page inside: sample stats and recent runs, under the shell’s own “Home” title.
import { SampleBadge } from '@rchursin/product/shell/layout'import { TailwindPlusAppShell } from '@rchursin/screens'
/** * The sidebar layout with your page inside. The shell draws the page's <h1> ("Home") above * your content in both themes, so the content starts with a subtitle. The sidebar shows from * 768px up. */const stats = [ { label: 'Runs this week', value: '1,284' }, { label: 'Skills used', value: '11 of 81' }, { label: 'Cost per finished task', value: '$1.84' },]
const runs = [ { name: 'pr-review', result: 'Finished', time: '4 min' }, { name: 'release-notes', result: 'Finished', time: '2 min' }, { name: 'test-fixer', result: 'Stopped after 3 retries', time: '19 min' }, { name: 'code-explorer', result: 'Finished', time: '6 min' },]Tailwind Plus sign-in
The simple sign-in form: email, password, a forgotten-password link and a sign-up link.
import { TailwindPlusSignIn } from '@rchursin/screens'
/** * The simple sign-in form. Its title is an <h2>, so the page adds a visually hidden <h1> * and a <main>. The form only prevents the default submit: connect your own handler. */export default function TailwindPlusSignInPage() { return ( <main> <h1 className="sr-only">Sign in to RChursin_</h1> <TailwindPlusSignIn /> </main> )}Tailwind Plus navbar
The simple marketing header over an empty page. Below 1024 pixels the links fold behind a menu button.
import { TailwindPlusNavbar } from '@rchursin/screens'
/** * The simple marketing header over an empty page. Below 1024px the links fold behind a menu * button, which has no menu yet: wire it to your own. */export default function TailwindPlusNavbarPage() { return ( <div className="min-h-dvh bg-white dark:bg-neutral-950"> <TailwindPlusNavbar /> <main className="mx-auto flex max-w-7xl flex-col gap-6 px-6 pt-10 pb-24 lg:px-8"> <h1 className="max-w-2xl text-4xl font-semibold tracking-tight text-balance text-neutral-900 dark:text-white">[Your page title]</h1> <p className="max-w-xl text-lg/8 text-pretty text-neutral-600 dark:text-neutral-400">[One line on what this page is for.]</p> <div className="flex min-h-80 items-center justify-center rounded-lg border-2 border-dashed border-neutral-200 text-sm text-neutral-500 dark:border-white/10 dark:text-neutral-400"> Page content </div> </main> </div> )}Tailwind Plus loading screen
The mark on a tile, a spinner with a message and two pulsing lines.
import { TailwindPlusLoading } from '@rchursin/screens'
/** * A full-screen loading state: the mark on a tile, a spinner with a message (a role="status" * region, so it is announced) and two pulsing lines. The page adds a <main> and a hidden <h1>. */export default function TailwindPlusLoadingPage() { return ( <main> <h1 className="sr-only">Loading your workspace</h1> <TailwindPlusLoading message="Loading your workspace…" /> </main> )}API
TailwindPlusAppShell props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | – | The page inside <main>. Without it, a dashed “Page content” box. |
className | string | 'min-h-dvh' | Classes on the root. Sets the height: pass your own (e.g. "h-[640px]") to fit a frame. |
TailwindPlusSignIn props
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | 'min-h-dvh' | Classes on the root. Sets the height: pass your own (e.g. "h-[640px]") to fit a frame. |
TailwindPlusLoading props
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | 'min-h-dvh' | Classes on the root. Sets the height: pass your own (e.g. "h-[640px]") to fit a frame. |
message | string | 'Loading your workspace…' | The words next to the spinner, inside the status region. |
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves through the links, fields and buttons in reading order. Every control is a native element. |
| Enter | In a sign-in form: submits it. The form only calls preventDefault(), so connect your own handler. |
- The app shell has a navigation named “Main” with aria-current="page" on Home, a search field with a hidden label, and a notifications button named “View notifications”.
- The shell draws the page’s <h1> (“Home”) at the top of <main> in both themes, so your content should start below it, with <h2> headings.
- The sidebar is hidden below 768 pixels and there is no phone menu yet; the Navbar’s menu button has a name but opens nothing.
- The loading screen is a role="status" region, so its message is announced; its spinner and pulsing lines only move when motion is allowed (motion-safe).
- Sign-in fields have real labels and autocomplete (email, current-password). The title is an <h2>, so a page around it needs its own <h1>.
Guidelines
Do
- Copy the screen into your app and swap its placeholder links, [bracketed] copy and handlers for your own.
- Wrap a screen without a <main> in one, and give a sign-in or loading page an <h1> (hidden is fine), as the examples do.
- Keep the Tailwind Plus look within a screen: its spacing, radius and round-capped icons belong together.
Don’t
- Don’t ship the placeholder buttons that open nothing (menu triggers, search) without wiring them up.
- Don’t mix screens from two styles on one page.
- Don’t pass a height class that cuts off the content on a phone; min-h-dvh is the default for a reason.
In use
Related
- shadcn screensFive screens in the shadcn/ui style: a hero, an inset sidebar shell, a login card, a site header and a loading screen.
- Magic UI screensFive screens with Magic UI’s effects: retro grid, border beam, shiny and gradient text, shimmer buttons, ripples and a dock.
- Aceternity screensFive screens in the Aceternity style: a spotlight hero, a borderless sidebar, a signup-style form, a floating navbar and a lamp.
- Screen iconsFifteen small round-capped icons used by the library-style screens, each its own component.
- LogoThe 09 Frame mark and the RChursin_ wordmark in four lockups, drawn as one SVG that takes the text colour.
- Sign-in flowThe whole front door in one component: email first, password or passkey, GitHub or Google, sign-up with a code, and recovery.
Source
The component’s own files, exactly as they run on this site.
/** * Tailwind Plus style · Application shell with sidebar. * Light: dark neutral-900 sidebar + white page (classic sidebar layout). * Dark: Catalyst-style layout in true neutral greys — content sits on a raised neutral-900 panel. * Pass `children` for page content and `className` to control height (defaults to full viewport). */import type { ComponentType, ReactNode, SVGProps } from "react"import { useId } from "react"import { Logo } from "../../brand/Logo"import { cx } from "../../components/cx"import { ActivityIcon, BellIcon, FolderIcon, HomeIcon, SearchIcon, SlidersIcon,} from "../../components/icons"
type NavItem = { label: string; icon: ComponentType<SVGProps<SVGSVGElement>>; current?: boolean }