Skip to content

feat(dashboard): scaffold BetterBase Next.js dashboard app#5

Merged
Helal-maker merged 2 commits intomainfrom
codex/create-betterbase-dashboard
Feb 20, 2026
Merged

feat(dashboard): scaffold BetterBase Next.js dashboard app#5
Helal-maker merged 2 commits intomainfrom
codex/create-betterbase-dashboard

Conversation

@Helal-maker
Copy link
Copy Markdown
Owner

@Helal-maker Helal-maker commented Feb 20, 2026

Motivation

  • Provide a dogfooded, developer-first web UI for managing BetterBase projects using Next.js App Router and the project tech choices (TypeScript, Tailwind, shadcn-style primitives).
  • Deliver a minimal, runnable studio-style shell so subsequent phases (table browser, API explorer, auth manager, logs) can be iterated on.

Description

  • Added a new apps/dashboard Next.js 15 App Router app with strict TypeScript, Tailwind, PostCSS, and Bun-first scripts including package.json, tsconfig.json, next.config.ts, and tailwind.config.ts.
  • Implemented core UI scaffolding: app-level layout, TanStack Query Providers, responsive Sidebar, Header (with mobile drawer and theme toggle), KPI overview page, routes for tables, api, auth, logs, settings, and auth entry pages (/login, /signup), plus UI primitives (ui/*), charts (recharts) and table placeholders.
  • Wired basic dogfooding integration to @betterbase/client via src/lib/betterbase.ts and a small React Query hook useCurrentUser; added a local type shim src/types/betterbase-client.d.ts so strict typechecking passes while the client package emits its own build artifacts.
  • Fixed build/config issues encountered during the rollout by switching tailwind.config.ts darkMode to 'class', adding postcss.config.mjs that uses @tailwindcss/postcss, updating package.json deps (e.g. tailwind-merge version), and replacing CSS rules that caused Tailwind compile errors; included src/app/globals.css with theme CSS variables.

Testing

  • Ran workspace dependency install with bun install and saved lockfile successfully.
  • Ran TypeScript checks with bun run typecheck in apps/dashboard and it completed with no errors.
  • Performed a production build with bun run build in apps/dashboard and the Next build completed successfully.
  • Launched the dev server with bun run dev --port 3100 and validated the UI loaded by capturing a Playwright screenshot of the dashboard home page; the dev server started and pages rendered correctly.

Codex Task

Summary by CodeRabbit

  • New Features
    • Launched the BetterBase Dashboard with authentication (login/signup pages).
    • Added main dashboard displaying key metrics and API usage charts.
    • Introduced navigation for Tables, API management, Logs, Authentication, and Settings.
    • Enabled dark mode theme switching.
    • Created responsive layout with sidebar navigation and header controls.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

Creates a new Next.js 15 dashboard application for BetterBase with authentication pages, dashboard layout with navigation, multiple feature pages, reusable UI components, client integration, TanStack Query setup, and Tailwind CSS styling. Includes 35+ new files establishing app structure, configuration, and foundational features.

Changes

Cohort / File(s) Summary
Configuration & Setup
next.config.ts, tsconfig.json, next-env.d.ts, postcss.config.mjs, tailwind.config.ts, package.json, README.md
Establishes Next.js 15 app configuration with strict TypeScript, Tailwind CSS, PostCSS, and dependencies (React 19, TanStack Query v5, Recharts, shadcn UI, Radix UI). Updates documentation with concrete tech stack and development instructions.
Root Layout & Global Styles
src/app/layout.tsx, src/app/globals.css, src/components/providers.tsx
Sets up root HTML structure with Inter font, metadata, Providers wrapper for TanStack Query client initialization. Global CSS defines comprehensive theme variables for light/dark modes with HSL color tokens.
Layout Components
src/components/layout/header.tsx, src/components/layout/sidebar.tsx, src/app/(dashboard)/layout.tsx
Implements dashboard layout with persistent two-column grid, responsive Header with theme toggle and user dropdown, and Sidebar with navigation items (Tables, API, Auth, Logs, Settings) and active route detection.
Authentication Pages
src/app/(auth)/login/page.tsx, src/app/(auth)/signup/page.tsx
Scaffolds Login and Signup pages with centered Card UI and placeholder text indicating future auth form wiring. Includes cross-links between pages and card-based layout.
Dashboard Pages
src/app/(dashboard)/page.tsx, src/app/(dashboard)/tables/page.tsx, src/app/(dashboard)/tables/[table]/page.tsx, src/app/(dashboard)/api-explorer/page.tsx, src/app/(dashboard)/auth/page.tsx, src/app/(dashboard)/logs/page.tsx, src/app/(dashboard)/settings/page.tsx
Creates dashboard pages with stat cards, API usage chart, quick actions, table browser/editor, API explorer, authentication manager, logs viewer, and settings—mostly scaffolded with phase completion indicators.
UI Components
src/components/ui/button.tsx, src/components/ui/card.tsx, src/components/ui/dropdown-menu.tsx
Implements reusable UI primitives: Button with variants (default, destructive, outline, secondary, ghost), Card components (Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter), and DropdownMenu wrapper around Radix UI primitives with consistent styling.
Feature Components
src/components/tables/table-browser.tsx, src/components/tables/table-editor.tsx, src/components/charts/api-usage-chart.tsx
Builds table management UI with static table browser listing and placeholder editor; includes static API usage area chart with Recharts showing daily call data.
Client Integration & Utilities
src/lib/betterbase.ts, src/lib/utils.ts, src/hooks/use-betterbase.ts, src/types/betterbase-client.d.ts
Establishes BetterBase client initialization with NEXT_PUBLIC_BETTERBASE_URL config, TanStack Query hook for fetching current user, className utility (cn) for Tailwind class composition, and TypeScript ambient types for @betterbase/client module API.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A dashboard springs forth, neat and bright,
With cards and charts, configured just right!
Tables and pages in ordered array,
Next.js and Tailwind light the way—
BetterBase beckons, ready to play! 🎨✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(dashboard): scaffold BetterBase Next.js dashboard app' accurately and concisely describes the primary change—a complete Next.js dashboard scaffold. It uses conventional commit format, is specific to the main deliverable, and clearly communicates the scope and intent to teammates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/create-betterbase-dashboard

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
betterbase/apps/dashboard/src/components/layout/header.tsx (2)

24-29: Theme preference not persisted.

The toggle updates the DOM class but doesn't persist to localStorage. On page refresh, the theme resets. For a scaffold this is acceptable, but consider adding persistence when implementing full theme support.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@betterbase/apps/dashboard/src/components/layout/header.tsx` around lines 24 -
29, The toggleTheme function updates the DOM and state but doesn't persist the
user's preference; update toggleTheme to write the chosen theme to localStorage
(e.g., set 'theme' = 'dark' or 'light') when calling setIsDark, and add
initialization logic (e.g., in a useEffect) to read localStorage on mount and
apply the stored value to document.documentElement.classList and setIsDark so
the preference survives page refreshes; reference the existing toggleTheme
function, setIsDark state setter, and document.documentElement when implementing
this.

18-22: Theme state may flash on hydration.

Initial useState(false) followed by useEffect sync can cause a brief mismatch if the page loads in dark mode (server renders light icon, client corrects to dark). Consider initializing from a cookie or using suppressHydrationWarning on the icon container.

💡 Alternative: check system preference or cookie for initial state
-  const [isDark, setIsDark] = useState(false);
+  const [isDark, setIsDark] = useState(() => {
+    if (typeof window !== 'undefined') {
+      return document.documentElement.classList.contains('dark');
+    }
+    return false;
+  });

Note: This still won't fully solve SSR mismatch. For robust theming, consider a dedicated theme provider (e.g., next-themes).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@betterbase/apps/dashboard/src/components/layout/header.tsx` around lines 18 -
22, The current useState(false) + useEffect sync for isDark causes a hydration
flash; change the initialization to a function that reads a persisted preference
(cookie/localStorage) or system setting via window.matchMedia and return that
value to useState (e.g., replace useState(false) with useState(() => { /* read
cookie or matchMedia */ })), and/or add suppressHydrationWarning on the icon
container that uses isDark to avoid the SSR mismatch; update references to
isDark, setIsDark, and the useEffect that currently calls
document.documentElement.classList.contains('dark') so the initial client value
matches the rendered markup.
betterbase/apps/dashboard/src/components/layout/sidebar.tsx (1)

32-33: startsWith match can produce false positives.

pathname.startsWith('/api') will also highlight for paths like /api-keys or /api-explorer. If more specific matching is needed later, consider exact match or segment-based logic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@betterbase/apps/dashboard/src/components/layout/sidebar.tsx` around lines 32
- 33, The current active-check using pathname.startsWith(item.href) in the
navigation.map callback can produce false positives (e.g., '/api' matching
'/api-keys'); update the isActive logic in the sidebar component to use a
boundary-aware check such as pathname === item.href ||
pathname.startsWith(item.href + '/') or compare path segments (split pathname
and item.href and compare the first N segments) so only exact or child routes of
item.href match; modify the isActive assignment where
pathname.startsWith(item.href) is used to implement this safer matching.
betterbase/apps/dashboard/src/app/(dashboard)/page.tsx (1)

54-60: Consider adding type="button" to prevent accidental form submission.

While these buttons are not currently inside a form, adding type="button" explicitly is a defensive practice that prevents unintended behavior if the component is ever wrapped in a form context.

💡 Suggested improvement
               <button
                 key={action}
+                type="button"
                 className="w-full rounded-lg border border-zinc-200 p-3 text-left text-sm hover:bg-zinc-50 dark:border-zinc-800 dark:hover:bg-zinc-900"
               >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@betterbase/apps/dashboard/src/app/`(dashboard)/page.tsx around lines 54 - 60,
The mapped JSX button elements rendering {action} lack an explicit type and can
submit a surrounding form unexpectedly; update the button element in the mapping
(the JSX that uses key={action} and renders {action}) to include type="button"
so it will never act as a submit button if this component is later placed inside
a form.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@betterbase/apps/dashboard/next-env.d.ts`:
- Around line 1-3: Remove the unused triple-slash reference to typed routes by
deleting the line containing /// <reference path="./.next/types/routes.d.ts" />
from the file; this will stop tsc --noEmit from failing in clean checkouts when
typedRoutes is not enabled in next.config.ts and the referenced routes.d.ts does
not exist.

In `@betterbase/apps/dashboard/src/components/layout/sidebar.tsx`:
- Line 10: The sidebar entry { name: 'API', href: '/api', icon: Code } uses the
reserved Next.js /api path and can conflict with App Router API routes; change
the href to a non-reserved route such as '/api-explorer' (or '/api-docs') in the
sidebar component and update any corresponding Link/navigation usage and route
handlers that expect the old path so they point to the new route name (search
for occurrences of "/api" and the symbol name 'API' in Link/route references and
adjust them to '/api-explorer').

In `@betterbase/apps/dashboard/src/components/ui/card.tsx`:
- Around line 14-16: CardTitle's forwardRef generic types are wrong: it
currently uses HTMLParagraphElement but renders an <h3> (HTMLHeadingElement).
Update the forwardRef generics so the ref type and props use HTMLHeadingElement
(e.g., React.forwardRef<HTMLHeadingElement,
React.HTMLAttributes<HTMLHeadingElement>>) to match the rendered <h3> in the
CardTitle component.
- Around line 29-34: The export list exports CardFooter before it's declared,
causing CardFooter to be undefined; move the CardFooter declaration (the
React.forwardRef component named CardFooter and its displayName assignment)
above the export statement so the exported symbols (Card, CardHeader,
CardFooter, CardTitle, CardDescription, CardContent) reference initialized
bindings.

---

Nitpick comments:
In `@betterbase/apps/dashboard/src/app/`(dashboard)/page.tsx:
- Around line 54-60: The mapped JSX button elements rendering {action} lack an
explicit type and can submit a surrounding form unexpectedly; update the button
element in the mapping (the JSX that uses key={action} and renders {action}) to
include type="button" so it will never act as a submit button if this component
is later placed inside a form.

In `@betterbase/apps/dashboard/src/components/layout/header.tsx`:
- Around line 24-29: The toggleTheme function updates the DOM and state but
doesn't persist the user's preference; update toggleTheme to write the chosen
theme to localStorage (e.g., set 'theme' = 'dark' or 'light') when calling
setIsDark, and add initialization logic (e.g., in a useEffect) to read
localStorage on mount and apply the stored value to
document.documentElement.classList and setIsDark so the preference survives page
refreshes; reference the existing toggleTheme function, setIsDark state setter,
and document.documentElement when implementing this.
- Around line 18-22: The current useState(false) + useEffect sync for isDark
causes a hydration flash; change the initialization to a function that reads a
persisted preference (cookie/localStorage) or system setting via
window.matchMedia and return that value to useState (e.g., replace
useState(false) with useState(() => { /* read cookie or matchMedia */ })),
and/or add suppressHydrationWarning on the icon container that uses isDark to
avoid the SSR mismatch; update references to isDark, setIsDark, and the
useEffect that currently calls
document.documentElement.classList.contains('dark') so the initial client value
matches the rendered markup.

In `@betterbase/apps/dashboard/src/components/layout/sidebar.tsx`:
- Around line 32-33: The current active-check using
pathname.startsWith(item.href) in the navigation.map callback can produce false
positives (e.g., '/api' matching '/api-keys'); update the isActive logic in the
sidebar component to use a boundary-aware check such as pathname === item.href
|| pathname.startsWith(item.href + '/') or compare path segments (split pathname
and item.href and compare the first N segments) so only exact or child routes of
item.href match; modify the isActive assignment where
pathname.startsWith(item.href) is used to implement this safer matching.

Comment thread betterbase/apps/dashboard/next-env.d.ts Outdated
Comment thread betterbase/apps/dashboard/src/components/layout/sidebar.tsx Outdated
Comment thread betterbase/apps/dashboard/src/components/ui/card.tsx Outdated
Comment thread betterbase/apps/dashboard/src/components/ui/card.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
betterbase/apps/dashboard/src/app/(dashboard)/page.tsx (1)

23-26: Mark decorative icons as aria-hidden.

These icons appear purely decorative; add aria-hidden="true" (and optionally focusable="false") to avoid extra noise for screen readers.

♿ Suggested tweak
-              <stat.icon className="h-4 w-4 text-zinc-600 dark:text-zinc-400" />
+              <stat.icon aria-hidden="true" focusable="false" className="h-4 w-4 text-zinc-600 dark:text-zinc-400" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@betterbase/apps/dashboard/src/app/`(dashboard)/page.tsx around lines 23 - 26,
The decorative icon rendered as <stat.icon /> inside the CardHeader is not
marked for accessibility; update the JSX where <stat.icon className="h-4 w-4
..."/> is used (near CardHeader/CardTitle) to add aria-hidden="true" and
optionally focusable="false" so screen readers ignore it, e.g. set these
attributes on the stat.icon element to make it non-interactive decorative
content.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@betterbase/apps/dashboard/src/app/`(dashboard)/page.tsx:
- Around line 52-60: The Quick Actions buttons rendered in the map (the array
['Create new table','Test API endpoint','View logs'] and the corresponding
<button> elements in page.tsx) are non-functional; either wire each action to
its route using next/link (replace the button with Link or wrap it and navigate
to the correct path for each label) or make them explicitly disabled by adding
disabled/aria-disabled, a visual “coming soon” affordance and preventing pointer
events. Update the mapping logic that generates these action buttons so each
label maps to a route slug (e.g., create-table, test-api, logs) and use Link for
navigation, or set disabled plus a tooltip/aria-label if the routes don’t exist.

In `@betterbase/apps/dashboard/src/components/layout/header.tsx`:
- Around line 60-99: Several icon-only Button components (e.g., the mobile menu
Button with <Menu />, the close Button with <X />, the theme toggle Button that
calls toggleTheme, and Buttons with <HelpCircle />, <Bell />, and the user menu
trigger with <User />) are missing accessible labels; add meaningful aria-label
attributes (or visually-hidden text) to each icon-only Button (for example:
aria-label="Open menu", aria-label="Close menu", aria-label="Toggle theme",
aria-label="Help", aria-label="Notifications", aria-label="User menu") ensuring
the theme toggle still uses isDark state for the visual icon but includes an
unchanged aria-label, and update any Dialog.Close or DropdownMenuTrigger Buttons
accordingly so screen readers can announce their purpose.
- Around line 64-75: The Dialog.Content lacks an accessible label — add a
Dialog.Title inside the drawer content to provide a screen-reader title (it can
be visually hidden using an sr-only class if you don't want it visible). Locate
the Dialog.Content block (the component using Dialog.Content) and insert a
Dialog.Title element (for example immediately after Dialog.Content opens or
before Sidebar) with appropriate text, or wrap the Dialog.Title with the
"sr-only" utility so the title is present for assistive tech but not visible;
keep existing Dialog.Close and Sidebar as-is.

---

Nitpick comments:
In `@betterbase/apps/dashboard/src/app/`(dashboard)/page.tsx:
- Around line 23-26: The decorative icon rendered as <stat.icon /> inside the
CardHeader is not marked for accessibility; update the JSX where <stat.icon
className="h-4 w-4 ..."/> is used (near CardHeader/CardTitle) to add
aria-hidden="true" and optionally focusable="false" so screen readers ignore it,
e.g. set these attributes on the stat.icon element to make it non-interactive
decorative content.

Comment on lines +52 to +60
<div className="space-y-2">
{['Create new table', 'Test API endpoint', 'View logs'].map((action) => (
<button
type="button"
key={action}
className="w-full rounded-lg border border-zinc-200 p-3 text-left text-sm hover:bg-zinc-50 dark:border-zinc-800 dark:hover:bg-zinc-900"
>
{action}
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Quick Actions are non-functional — wire to routes or mark disabled.

These buttons render without handlers or navigation, so clicks do nothing. That’s a small but visible UX break. Suggest turning them into links (if the routes exist) or explicitly disabled with a “coming soon” affordance.

🛠️ Example: wire to routes via next/link
+import Link from 'next/link';
 import { ApiUsageChart } from '@/components/charts/api-usage-chart';
 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
 import { Activity, Code, Database, Users } from 'lucide-react';

 export default function DashboardPage() {
   const stats = [
     { name: 'Total Tables', value: '12', icon: Database, change: '+2 this week' },
     { name: 'API Calls', value: '45.2K', icon: Code, change: '+12% from last week' },
     { name: 'Active Users', value: '2,547', icon: Users, change: '+234 this week' },
     { name: 'Uptime', value: '99.9%', icon: Activity, change: '30 days' },
   ];
+  const actions = [
+    { label: 'Create new table', href: '/tables' },
+    { label: 'Test API endpoint', href: '/api' },
+    { label: 'View logs', href: '/logs' },
+  ];

   return (
@@
           <CardContent>
             <div className="space-y-2">
-              {['Create new table', 'Test API endpoint', 'View logs'].map((action) => (
-                <button
-                  type="button"
-                  key={action}
-                  className="w-full rounded-lg border border-zinc-200 p-3 text-left text-sm hover:bg-zinc-50 dark:border-zinc-800 dark:hover:bg-zinc-900"
-                >
-                  {action}
-                </button>
-              ))}
+              {actions.map((action) => (
+                <Link
+                  key={action.label}
+                  href={action.href}
+                  className="block w-full rounded-lg border border-zinc-200 p-3 text-left text-sm hover:bg-zinc-50 dark:border-zinc-800 dark:hover:bg-zinc-900"
+                >
+                  {action.label}
+                </Link>
+              ))}
             </div>
           </CardContent>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="space-y-2">
{['Create new table', 'Test API endpoint', 'View logs'].map((action) => (
<button
type="button"
key={action}
className="w-full rounded-lg border border-zinc-200 p-3 text-left text-sm hover:bg-zinc-50 dark:border-zinc-800 dark:hover:bg-zinc-900"
>
{action}
</button>
<div className="space-y-2">
{actions.map((action) => (
<Link
key={action.label}
href={action.href}
className="block w-full rounded-lg border border-zinc-200 p-3 text-left text-sm hover:bg-zinc-50 dark:border-zinc-800 dark:hover:bg-zinc-900"
>
{action.label}
</Link>
))}
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@betterbase/apps/dashboard/src/app/`(dashboard)/page.tsx around lines 52 - 60,
The Quick Actions buttons rendered in the map (the array ['Create new
table','Test API endpoint','View logs'] and the corresponding <button> elements
in page.tsx) are non-functional; either wire each action to its route using
next/link (replace the button with Link or wrap it and navigate to the correct
path for each label) or make them explicitly disabled by adding
disabled/aria-disabled, a visual “coming soon” affordance and preventing pointer
events. Update the mapping logic that generates these action buttons so each
label maps to a route slug (e.g., create-table, test-api, logs) and use Link for
navigation, or set disabled plus a tooltip/aria-label if the routes don’t exist.

Comment on lines +60 to +99
<Button variant="ghost" size="icon" className="md:hidden">
<Menu className="h-5 w-5" />
</Button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-40 bg-black/30" />
<Dialog.Content className="fixed inset-y-0 left-0 z-50 w-72 bg-white dark:bg-zinc-900">
<div className="flex items-center justify-end p-2">
<Dialog.Close asChild>
<Button variant="ghost" size="icon">
<X className="h-4 w-4" />
</Button>
</Dialog.Close>
</div>
<Sidebar mobile />
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
<h1 className="text-base font-semibold md:text-xl">My Project</h1>
<span className="rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-700 dark:bg-green-950 dark:text-green-400">
Active
</span>
</div>

<div className="flex items-center gap-2">
<Button variant="ghost" size="icon" onClick={toggleTheme} aria-label="Toggle theme">
<span suppressHydrationWarning>{isDark ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}</span>
</Button>
<Button variant="ghost" size="icon">
<HelpCircle className="h-5 w-5" />
</Button>
<Button variant="ghost" size="icon">
<Bell className="h-5 w-5" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<User className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add accessible labels to icon-only buttons.

Icon-only controls need aria-label (or SR-only text) so screen readers can announce their purpose.

✅ Suggested fix
-            <Button variant="ghost" size="icon" className="md:hidden">
+            <Button variant="ghost" size="icon" className="md:hidden" aria-label="Open navigation">
               <Menu className="h-5 w-5" />
             </Button>
...
-                  <Button variant="ghost" size="icon">
+                  <Button variant="ghost" size="icon" aria-label="Close navigation">
                     <X className="h-4 w-4" />
                   </Button>
...
-        <Button variant="ghost" size="icon">
+        <Button variant="ghost" size="icon" aria-label="Help">
           <HelpCircle className="h-5 w-5" />
         </Button>
-        <Button variant="ghost" size="icon">
+        <Button variant="ghost" size="icon" aria-label="Notifications">
           <Bell className="h-5 w-5" />
         </Button>
...
-            <Button variant="ghost" size="icon">
+            <Button variant="ghost" size="icon" aria-label="User menu">
               <User className="h-5 w-5" />
             </Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@betterbase/apps/dashboard/src/components/layout/header.tsx` around lines 60 -
99, Several icon-only Button components (e.g., the mobile menu Button with <Menu
/>, the close Button with <X />, the theme toggle Button that calls toggleTheme,
and Buttons with <HelpCircle />, <Bell />, and the user menu trigger with <User
/>) are missing accessible labels; add meaningful aria-label attributes (or
visually-hidden text) to each icon-only Button (for example: aria-label="Open
menu", aria-label="Close menu", aria-label="Toggle theme", aria-label="Help",
aria-label="Notifications", aria-label="User menu") ensuring the theme toggle
still uses isDark state for the visual icon but includes an unchanged
aria-label, and update any Dialog.Close or DropdownMenuTrigger Buttons
accordingly so screen readers can announce their purpose.

Comment on lines +64 to +75
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-40 bg-black/30" />
<Dialog.Content className="fixed inset-y-0 left-0 z-50 w-72 bg-white dark:bg-zinc-900">
<div className="flex items-center justify-end p-2">
<Dialog.Close asChild>
<Button variant="ghost" size="icon">
<X className="h-4 w-4" />
</Button>
</Dialog.Close>
</div>
<Sidebar mobile />
</Dialog.Content>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check Dialog usage in the header component
rg -n "Dialog\." betterbase/apps/dashboard/src/components/layout/header.tsx

Repository: Helal-maker/Betterbase

Length of output: 534


🏁 Script executed:

#!/bin/bash
# Check if Dialog.Title exists anywhere in the file
cat -n betterbase/apps/dashboard/src/components/layout/header.tsx | sed -n '58,80p'

Repository: Helal-maker/Betterbase

Length of output: 1275


🏁 Script executed:

#!/bin/bash
# Search for other Dialog.Content usages in the codebase to check patterns
rg -B2 -A3 "Dialog\.Content" betterbase/apps/dashboard/src --type tsx --type ts | head -50

Repository: Helal-maker/Betterbase

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Search for other Dialog.Content usages to check patterns
rg -B2 -A3 "Dialog\.Content" betterbase/apps/dashboard/src | head -80

Repository: Helal-maker/Betterbase

Length of output: 1345


🌐 Web query:

Radix Dialog accessibility requirements Dialog.Title screen reader

💡 Result:

Radix Dialog.Content must have an accessible name so screen readers can announce what the dialog is. Radix’s recommended/expected way to provide that name is <Dialog.Title>. (radix-ui.com)

What to do (requirements + options)

  1. Include Dialog.Title inside the dialog

    • Radix describes Dialog.Title as “an accessible title to be announced when the dialog is opened.” (radix-ui.com)
  2. If you don’t want a visible title, still render it (screen-reader-only)

    • Wrap the title with Radix VisuallyHidden so it remains available to assistive tech. (radix-ui.com)
  3. Alternative (less common): label the dialog via ARIA

    • A dialog can be named via aria-label or aria-labelledby (this is what tools like axe check under “aria-dialog-name”). (developer.mozilla.org)
    • In Radix, using Dialog.Title effectively supports this requirement by providing the title that’s announced. (radix-ui.com)

Example (recommended)

<Dialog.Content>
  <Dialog.Title>Account settings</Dialog.Title>
  ...
</Dialog.Content>

Example (title visually hidden, still accessible)

<Dialog.Content>
  <VisuallyHidden asChild>
    <Dialog.Title>Account settings</Dialog.Title>
  </VisuallyHidden>
  ...
</Dialog.Content>

If you’re seeing Radix’s console error (“DialogContent requires a DialogTitle…”), it’s that same accessibility requirement being enforced. (github.com)

Citations:


Add Dialog.Title to make the drawer accessible.

Dialog.Content requires a title for screen readers. Either add a visible Dialog.Title or wrap it with sr-only to keep it visually hidden.

✅ Suggested fix
           <Dialog.Content className="fixed inset-y-0 left-0 z-50 w-72 bg-white dark:bg-zinc-900">
+            <Dialog.Title className="sr-only">Navigation</Dialog.Title>
             <div className="flex items-center justify-end p-2">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@betterbase/apps/dashboard/src/components/layout/header.tsx` around lines 64 -
75, The Dialog.Content lacks an accessible label — add a Dialog.Title inside the
drawer content to provide a screen-reader title (it can be visually hidden using
an sr-only class if you don't want it visible). Locate the Dialog.Content block
(the component using Dialog.Content) and insert a Dialog.Title element (for
example immediately after Dialog.Content opens or before Sidebar) with
appropriate text, or wrap the Dialog.Title with the "sr-only" utility so the
title is present for assistive tech but not visible; keep existing Dialog.Close
and Sidebar as-is.

@Helal-maker Helal-maker merged commit 6093550 into main Feb 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant