Docs
Layout

Layout

Guidelines to create consistent layouts across Studio pages using a set of page components.

The Page pattern consists of three main components that work together to create consistent page layouts: PageContainer, PageHeader, and PageSection. These components provide a structured approach to building pages with consistent spacing, max-widths, and content organization.

Layout Types

Settings

Settings pages are used for configuration and preference management. They follow a single-column layout with default widths to keep content focused and readable. Examples include project settings or auth sessions.

  • Use PageHeader with size="default"
  • Use PageContainer with size="default"
  • Use PageSection for organizing settings into logical groups
Loading...
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import {
  Button,
  Card,
  CardContent,
  CardFooter,
  FormControl_Shadcn_,
  FormField_Shadcn_,
  Form_Shadcn_,
  Input_Shadcn_,
  PrePostTab,
  Switch,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { PageContainer } from 'ui-patterns/PageContainer'
import {
  PageHeader,
  PageHeaderDescription,
  PageHeaderMeta,
  PageHeaderSummary,
  PageHeaderTitle,
} from 'ui-patterns/PageHeader'
import {
  PageSection,
  PageSectionContent,
  PageSectionDescription,
  PageSectionMeta,
  PageSectionSummary,
  PageSectionTitle,
} from 'ui-patterns/PageSection'
import * as z from 'zod'
 
const RefreshTokenSchema = z.object({
  REFRESH_TOKEN_ROTATION_ENABLED: z.boolean(),
  SECURITY_REFRESH_TOKEN_REUSE_INTERVAL: z.coerce.number().min(0),
})
 
const UserSessionsSchema = z.object({
  SESSIONS_TIMEBOX: z.coerce.number().min(0),
  SESSIONS_INACTIVITY_TIMEOUT: z.coerce.number().min(0),
  SESSIONS_SINGLE_PER_USER: z.boolean(),
})
 
function HoursOrNeverText({ value }: { value: number }) {
  if (value === 0) {
    return 'never'
  } else if (value === 1) {
    return 'hour'
  } else {
    return 'hours'
  }
}
 
export function PageLayoutSettings() {
  const refreshTokenForm = useForm<z.infer<typeof RefreshTokenSchema>>({
    resolver: zodResolver(RefreshTokenSchema),
    defaultValues: {
      REFRESH_TOKEN_ROTATION_ENABLED: false,
      SECURITY_REFRESH_TOKEN_REUSE_INTERVAL: 10,
    },
  })
 
  const userSessionsForm = useForm<z.infer<typeof UserSessionsSchema>>({
    resolver: zodResolver(UserSessionsSchema),
    defaultValues: {
      SESSIONS_TIMEBOX: 0,
      SESSIONS_INACTIVITY_TIMEOUT: 0,
      SESSIONS_SINGLE_PER_USER: false,
    },
  })
 
  return (
    <div className="w-full">
      <PageHeader size="default">
        <PageHeaderMeta>
          <PageHeaderSummary>
            <PageHeaderTitle>User Sessions</PageHeaderTitle>
            <PageHeaderDescription>
              Configure settings for user sessions and refresh tokens
            </PageHeaderDescription>
          </PageHeaderSummary>
        </PageHeaderMeta>
      </PageHeader>
 
      <PageContainer size="default">
        <PageSection>
          <PageSectionMeta>
            <PageSectionSummary>
              <PageSectionTitle>Refresh Tokens</PageSectionTitle>
              <PageSectionDescription>
                Configure refresh token rotation and security settings.
              </PageSectionDescription>
            </PageSectionSummary>
          </PageSectionMeta>
          <PageSectionContent>
            <Form_Shadcn_ {...refreshTokenForm}>
              <form className="space-y-4">
                <Card>
                  <CardContent className="pt-6">
                    <FormField_Shadcn_
                      control={refreshTokenForm.control}
                      name="REFRESH_TOKEN_ROTATION_ENABLED"
                      render={({ field }) => (
                        <FormItemLayout
                          layout="flex-row-reverse"
                          label="Detect and revoke potentially compromised refresh tokens"
                          description="Prevent replay attacks from potentially compromised refresh tokens."
                        >
                          <FormControl_Shadcn_>
                            <Switch checked={field.value} onCheckedChange={field.onChange} />
                          </FormControl_Shadcn_>
                        </FormItemLayout>
                      )}
                    />
                  </CardContent>
                  <CardContent>
                    <FormField_Shadcn_
                      control={refreshTokenForm.control}
                      name="SECURITY_REFRESH_TOKEN_REUSE_INTERVAL"
                      render={({ field }) => (
                        <FormItemLayout
                          layout="flex-row-reverse"
                          label="Refresh token reuse interval"
                          description="Time interval where the same refresh token can be used multiple times to request for an access token. Recommendation: 10 seconds."
                        >
                          <FormControl_Shadcn_>
                            <PrePostTab postTab="seconds">
                              <Input_Shadcn_ type="number" min={0} {...field} />
                            </PrePostTab>
                          </FormControl_Shadcn_>
                        </FormItemLayout>
                      )}
                    />
                  </CardContent>
                  <CardFooter className="justify-end space-x-2">
                    {refreshTokenForm.formState.isDirty && (
                      <Button type="default" onClick={() => refreshTokenForm.reset()}>
                        Cancel
                      </Button>
                    )}
                    <Button
                      type="primary"
                      htmlType="submit"
                      disabled={!refreshTokenForm.formState.isDirty}
                    >
                      Save changes
                    </Button>
                  </CardFooter>
                </Card>
              </form>
            </Form_Shadcn_>
          </PageSectionContent>
        </PageSection>
 
        <PageSection>
          <PageSectionMeta>
            <PageSectionSummary>
              <PageSectionTitle>User Sessions</PageSectionTitle>
              <PageSectionDescription>
                Configure session timeout and single session enforcement settings.
              </PageSectionDescription>
            </PageSectionSummary>
          </PageSectionMeta>
          <PageSectionContent>
            <Form_Shadcn_ {...userSessionsForm}>
              <form className="space-y-4">
                <Card>
                  <CardContent>
                    <FormField_Shadcn_
                      control={userSessionsForm.control}
                      name="SESSIONS_SINGLE_PER_USER"
                      render={({ field }) => (
                        <FormItemLayout
                          layout="flex-row-reverse"
                          label="Enforce single session per user"
                          description="If enabled, all but a user's most recently active session will be terminated."
                        >
                          <FormControl_Shadcn_>
                            <Switch checked={field.value} onCheckedChange={field.onChange} />
                          </FormControl_Shadcn_>
                        </FormItemLayout>
                      )}
                    />
                  </CardContent>
 
                  <CardContent>
                    <FormField_Shadcn_
                      control={userSessionsForm.control}
                      name="SESSIONS_TIMEBOX"
                      render={({ field }) => (
                        <FormItemLayout
                          layout="flex-row-reverse"
                          label="Time-box user sessions"
                          description="The amount of time before a user is forced to sign in again. Use 0 for never."
                        >
                          <div className="flex items-center">
                            <FormControl_Shadcn_>
                              <PrePostTab postTab={<HoursOrNeverText value={field.value || 0} />}>
                                <Input_Shadcn_ type="number" min={0} {...field} />
                              </PrePostTab>
                            </FormControl_Shadcn_>
                          </div>
                        </FormItemLayout>
                      )}
                    />
                  </CardContent>
 
                  <CardContent>
                    <FormField_Shadcn_
                      control={userSessionsForm.control}
                      name="SESSIONS_INACTIVITY_TIMEOUT"
                      render={({ field }) => (
                        <FormItemLayout
                          layout="flex-row-reverse"
                          label="Inactivity timeout"
                          description="The amount of time a user needs to be inactive to be forced to sign in again. Use 0 for never."
                        >
                          <div className="flex items-center">
                            <FormControl_Shadcn_>
                              <PrePostTab postTab={<HoursOrNeverText value={field.value || 0} />}>
                                <Input_Shadcn_ type="number" {...field} />
                              </PrePostTab>
                            </FormControl_Shadcn_>
                          </div>
                        </FormItemLayout>
                      )}
                    />
                  </CardContent>
 
                  <CardFooter className="justify-end space-x-2">
                    {userSessionsForm.formState.isDirty && (
                      <Button type="default" onClick={() => userSessionsForm.reset()}>
                        Cancel
                      </Button>
                    )}
                    <Button
                      type="primary"
                      htmlType="submit"
                      disabled={!userSessionsForm.formState.isDirty}
                    >
                      Save changes
                    </Button>
                  </CardFooter>
                </Card>
              </form>
            </Form_Shadcn_>
          </PageSectionContent>
        </PageSection>
      </PageContainer>
    </div>
  )
}

List

List pages display collections of objects like tables, triggers, or functions. These pages use larger widths to accommodate wide content like data tables. Examples include database triggers, database functions or org team members.

  • Use PageHeader with size="large"
  • Use PageContainer with size="large"
  • Use PageSection to wrap list content

Table and List Actions:

  • With filters or search: If the table has filters or search, place table actions aligned with the filters on the right side. Do not use PageSectionAside or PageHeaderAside for table actions when filters are present.
  • Without filters: For simple lists without filters or search, add primary list actions to PageHeaderAside or PageSectionAside as appropriate.
Loading...
import { Search } from 'lucide-react'
import React from 'react'
import {
  Button,
  Card,
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
  Input,
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from 'ui'
import { PageContainer } from 'ui-patterns/PageContainer'
import {
  PageHeader,
  PageHeaderDescription,
  PageHeaderMeta,
  PageHeaderSummary,
  PageHeaderTitle,
} from 'ui-patterns/PageHeader'
import { PageSection, PageSectionContent } from 'ui-patterns/PageSection'
 
export function PageLayoutList(): React.JSX.Element {
  const functions = [
    {
      id: 1,
      name: 'get_user_profile',
      arguments: 'user_id uuid',
      return_type: 'jsonb',
      security: 'Definer',
    },
    {
      id: 2,
      name: 'update_user_settings',
      arguments: 'user_id uuid, settings jsonb',
      return_type: 'void',
      security: 'Invoker',
    },
    {
      id: 3,
      name: 'calculate_total',
      arguments: 'amount numeric, tax_rate numeric',
      return_type: 'numeric',
      security: 'Definer',
    },
  ]
 
  return (
    <div className="w-full">
      <PageHeader size="large">
        <PageHeaderMeta>
          <PageHeaderSummary>
            <PageHeaderTitle>Database Functions</PageHeaderTitle>
            <PageHeaderDescription>Manage your database functions</PageHeaderDescription>
          </PageHeaderSummary>
        </PageHeaderMeta>
      </PageHeader>
 
      <PageContainer size="large">
        <PageSection>
          <PageSectionContent>
            <div className="w-full space-y-4">
              <div className="flex flex-col lg:flex-row lg:items-center justify-between gap-2 flex-wrap">
                <div className="flex flex-col lg:flex-row lg:items-center gap-2">
                  <Input
                    placeholder="Search for a function"
                    size="tiny"
                    icon={<Search />}
                    className="w-full lg:w-52"
                  />
                </div>
                <Button type="primary">Create a new function</Button>
              </div>
 
              <Card>
                <Table className="table-fixed overflow-x-auto">
                  <TableHeader>
                    <TableRow>
                      <TableHead>Name</TableHead>
                      <TableHead className="table-cell">Arguments</TableHead>
                      <TableHead className="table-cell">Return type</TableHead>
                      <TableHead className="table-cell w-[100px]">Security</TableHead>
                      <TableHead className="w-1/6"></TableHead>
                    </TableRow>
                  </TableHeader>
                  <TableBody>
                    {functions.map((fn) => (
                      <TableRow key={fn.id}>
                        <TableCell className="font-medium">{fn.name}</TableCell>
                        <TableCell>{fn.arguments}</TableCell>
                        <TableCell>{fn.return_type}</TableCell>
                        <TableCell>{fn.security}</TableCell>
                        <TableCell>
                          <DropdownMenu>
                            <DropdownMenuTrigger asChild>
                              <Button type="text" size="small">
                                Actions
                              </Button>
                            </DropdownMenuTrigger>
                            <DropdownMenuContent align="end">
                              <DropdownMenuItem>Edit</DropdownMenuItem>
                              <DropdownMenuItem>Duplicate</DropdownMenuItem>
                              <DropdownMenuSeparator />
                              <DropdownMenuItem className="text-destructive">
                                Delete
                              </DropdownMenuItem>
                            </DropdownMenuContent>
                          </DropdownMenu>
                        </TableCell>
                      </TableRow>
                    ))}
                  </TableBody>
                </Table>
              </Card>
            </div>
          </PageSectionContent>
        </PageSection>
      </PageContainer>
    </div>
  )
}
Loading...
import { PageContainer } from 'ui-patterns/PageContainer'
import {
  PageHeader,
  PageHeaderMeta,
  PageHeaderSummary,
  PageHeaderTitle,
  PageHeaderDescription,
  PageHeaderAside,
} from 'ui-patterns/PageHeader'
import { PageSection, PageSectionContent } from 'ui-patterns/PageSection'
import {
  Button,
  Card,
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from 'ui'
 
export function PageLayoutListSimple() {
  const items = [
    { id: 1, name: 'Project Alpha', status: 'Active', members: 12 },
    { id: 2, name: 'Project Beta', status: 'Active', members: 8 },
    { id: 3, name: 'Project Gamma', status: 'Inactive', members: 5 },
  ]
 
  return (
    <div className="w-full">
      <PageHeader size="large">
        <PageHeaderMeta>
          <PageHeaderSummary>
            <PageHeaderTitle>Projects</PageHeaderTitle>
            <PageHeaderDescription>
              Manage and view all your projects in one place.
            </PageHeaderDescription>
          </PageHeaderSummary>
          <PageHeaderAside>
            <Button type="primary" size="small">
              Create Project
            </Button>
          </PageHeaderAside>
        </PageHeaderMeta>
      </PageHeader>
 
      <PageContainer size="large">
        <PageSection>
          <PageSectionContent>
            <Card>
              <Table>
                <TableHeader>
                  <TableRow>
                    <TableHead>Name</TableHead>
                    <TableHead>Status</TableHead>
                    <TableHead>Members</TableHead>
                    <TableHead className="text-right">Actions</TableHead>
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {items.map((item) => (
                    <TableRow key={item.id}>
                      <TableCell className="font-medium">{item.name}</TableCell>
                      <TableCell>{item.status}</TableCell>
                      <TableCell>{item.members}</TableCell>
                      <TableCell className="text-right">
                        <DropdownMenu>
                          <DropdownMenuTrigger asChild>
                            <Button type="text" size="small">
                              Actions
                            </Button>
                          </DropdownMenuTrigger>
                          <DropdownMenuContent align="end">
                            <DropdownMenuItem>Edit</DropdownMenuItem>
                            <DropdownMenuItem>Duplicate</DropdownMenuItem>
                            <DropdownMenuSeparator />
                            <DropdownMenuItem className="text-destructive">Delete</DropdownMenuItem>
                          </DropdownMenuContent>
                        </DropdownMenu>
                      </TableCell>
                    </TableRow>
                  ))}
                </TableBody>
              </Table>
            </Card>
          </PageSectionContent>
        </PageSection>
      </PageContainer>
    </div>
  )
}

Data and Full Page Experiences

Full-page experiences like the table editor, cron jobs, and edge functions require maximum screen real-estate and so make use of "full" size containers.

  • Use PageHeader with size="full"
  • Use PageContainer with size="full"
  • Content spans the full width of the viewport

Detail Pages

Detail pages display dense or lengthy content split into multiple sections. The horizontal orientation allows for better information hierarchy and context. Examples include organisation billing or project infrastructure.

  • Use PageHeader with size="large"
  • Use PageContainer with size="large"
  • Use PageSection with orientation="horizontal" to show summary alongside content
  • Multiple sections can stack vertically with horizontal layouts within each
Loading...
import { Button, Card, CardContent } from 'ui'
import { PageContainer } from 'ui-patterns/PageContainer'
import {
  PageHeader,
  PageHeaderDescription,
  PageHeaderMeta,
  PageHeaderSummary,
  PageHeaderTitle,
} from 'ui-patterns/PageHeader'
import {
  PageSection,
  PageSectionContent,
  PageSectionDescription,
  PageSectionMeta,
  PageSectionSummary,
  PageSectionTitle,
} from 'ui-patterns/PageSection'
 
export function PageLayoutDetail() {
  return (
    <div className="w-full">
      <PageHeader size="large">
        <PageHeaderMeta>
          <PageHeaderSummary>
            <PageHeaderTitle>Billing</PageHeaderTitle>
            <PageHeaderDescription>
              Manage your organization&apos;s billing and subscription settings.
            </PageHeaderDescription>
          </PageHeaderSummary>
        </PageHeaderMeta>
      </PageHeader>
 
      <PageContainer size="large">
        <PageSection orientation="horizontal">
          <PageSectionMeta>
            <PageSectionSummary>
              <PageSectionTitle>Subscription</PageSectionTitle>
              <PageSectionDescription>
                View and manage your current subscription plan and billing cycle.
              </PageSectionDescription>
            </PageSectionSummary>
          </PageSectionMeta>
          <PageSectionContent>
            <Card>
              <CardContent className="p-6">
                <div className="space-y-4">
                  <div>
                    <p className="text-xs text-foreground-lighter mb-1">Current Plan</p>
                    <p className="text-sm font-medium">Pro Plan</p>
                  </div>
                  <div>
                    <p className="text-xs text-foreground-lighter mb-1">Billing Cycle</p>
                    <p className="text-sm">Monthly</p>
                  </div>
                  <div>
                    <p className="text-xs text-foreground-lighter mb-1">Next Billing Date</p>
                    <p className="text-sm">March 15, 2024</p>
                  </div>
                  <div className="pt-2">
                    <Button type="default" size="small">
                      Change Plan
                    </Button>
                  </div>
                </div>
              </CardContent>
            </Card>
          </PageSectionContent>
        </PageSection>
 
        <PageSection orientation="horizontal">
          <PageSectionMeta>
            <PageSectionSummary>
              <PageSectionTitle>Cost control</PageSectionTitle>
              <PageSectionDescription>
                Set spending limits and alerts to manage your organization&apos;s costs.
              </PageSectionDescription>
            </PageSectionSummary>
          </PageSectionMeta>
          <PageSectionContent>
            <Card>
              <CardContent className="p-6">
                <div className="space-y-4">
                  <div>
                    <p className="text-xs text-foreground-lighter mb-1">Monthly Spending Limit</p>
                    <p className="text-sm">$500.00</p>
                  </div>
                  <div>
                    <p className="text-xs text-foreground-lighter mb-1">Current Month Spend</p>
                    <p className="text-sm">$234.50</p>
                  </div>
                  <div className="pt-2">
                    <Button type="default" size="small">
                      Configure Limits
                    </Button>
                  </div>
                </div>
              </CardContent>
            </Card>
          </PageSectionContent>
        </PageSection>
 
        <PageSection orientation="horizontal">
          <PageSectionMeta>
            <PageSectionSummary>
              <PageSectionTitle>Payment Methods</PageSectionTitle>
              <PageSectionDescription>
                Manage payment methods and billing information for your organization.
              </PageSectionDescription>
            </PageSectionSummary>
          </PageSectionMeta>
          <PageSectionContent>
            <Card>
              <CardContent className="p-6">
                <div className="space-y-4">
                  <div>
                    <p className="text-xs text-foreground-lighter mb-1">Primary Payment Method</p>
                    <p className="text-sm">•••• •••• •••• 4242</p>
                  </div>
                  <div>
                    <p className="text-xs text-foreground-lighter mb-1">Expires</p>
                    <p className="text-sm">12/2025</p>
                  </div>
                  <div className="pt-2">
                    <Button type="default" size="small">
                      Update Payment Method
                    </Button>
                  </div>
                </div>
              </CardContent>
            </Card>
          </PageSectionContent>
        </PageSection>
      </PageContainer>
    </div>
  )
}

Components

  • PageContainer - Container component providing consistent max-width and padding based on size variants
  • PageHeader - Compound component for building page headers with breadcrumbs, icons, titles, descriptions, actions, and navigation
  • PageSection - Compound component for organizing page content into distinct sections with title, description, and action areas