SchemaExplorer
A collapsible Postgres schema tree from tables to columns, indexes, and relations, with types and search.
SchemaExplorer browses a database’s schema as a keyboard-native tree. Each table expands to its columns (with SQL types, primary keys, foreign keys, unique constraints, and nullability), its indexes, and its relations. Search reveals matching descendants and highlights the match. It is presentational: the schema comes from your backend, usually via information_schema introspection.
public
3 tables
Behavior
- Expand click a table to reveal its columns, indexes, and relations. Several tables can stay open at once.
- Types each column shows its SQL type; primary keys wear a key, unique columns a
uniquetag, and nullable columns a faintnull. - Relations foreign-key columns are listed under Relations as
column → table.column. - Search press
/from the tree, then search table names, column names, index names, or Postgres types. Matching descendants open automatically. - Keyboard use Up/Down to move, Right to open or enter a table, Left to close or return to its parent, Home/End to jump, and typeahead to find a visible row.
Install
npx shadcn@latest add https://ui.neon.com/r/schema-explorer.json
Usage
import { SchemaExplorer } from "@/components/neon-ui/schema-explorer";
<SchemaExplorer tables={tables} defaultExpanded={["users"]} />;
tables is an array of { name, columns, indexes?, rowCount? }. Each column is { name, type, primaryKey?, unique?, nullable?, references? }.
Wiring to Neon
A server component introspects the public schema over the Neon serverless driver and maps the rows to tables:
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL);
const rows = await sql`
SELECT table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public'
ORDER BY table_name, ordinal_position
`;
// group rows by table_name into { name, columns } and add PK/FK from
// information_schema.table_constraints
It runs server-side, so the connection string never reaches the browser. Add indexes from pg_indexes if you want the Indexes section populated.
Props
tablesTable[]
The tables to browse. Each is { name, schema?, columns, indexes?, rowCount? }.
Table[]defaultExpanded?string[]
Table names expanded on first render.
string[]expanded?string[]
Expanded table names when controlled.
string[]onExpandedChange?(expanded: string[]) => void
Called whenever a table opens or closes.
(expanded: string[]) => voidonColumnSelect?(table: Table, column: Column) => void
Called when a column row is activated.
(table: Table, column: Column) => voidonIndexSelect?(table: Table, index: Index) => void
Called when an index row is activated.
(table: Table, index: Index) => voidtitle?string
Panel heading (typically the schema name).
string"public"Accessibility
The component follows the WAI-ARIA tree pattern with one Tab stop, roving focus, arrow-key navigation, Home/End, typeahead, and aria-expanded on table rows. Focus stays visible over hover and open states. The only motion is the chevron turn, which snaps under reduced motion.