# How to Build a Mobile Responsive Layout with Panel Drawers in React

> Learn how to build a mobile responsive layout with panel drawers in React. This article explains using state-driven rendering and CSS media queries for optimal mobile experiences.

- Repository: [Microsoft/Ontology-Playground](https://github.com/microsoft/Ontology-Playground)
- Tags: getting-started
- Published: 2026-07-23

---

**The Ontology Playground achieves mobile responsiveness by combining React state-driven conditional rendering with CSS media queries that toggle a fixed-position drawer and bottom tab bar only on viewports narrower than 900px.**

The Microsoft Ontology-Playground demonstrates an elegant pattern for adapting complex desktop interfaces to mobile screens without duplicating components. In [`src/App.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/App.tsx) and [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css), the implementation uses a single piece of React state to control which panel displays in a full-screen drawer, while viewport-specific CSS rules handle the responsive visibility and positioning. This architecture maintains a clean separation between mobile and desktop layout logic.

## Managing Panel State with React Hooks

At the core of the mobile layout is a state variable that tracks which side-panel is currently active. In [`src/App.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/App.tsx) at lines 57‑58, the component declares:

```typescript
const [mobilePanel, setMobilePanel] = useState<'graph' | 'quests' | 'inspector' | 'query'>('graph');

```

When `mobilePanel` is set to anything other than `'graph'`, the application displays the drawer overlay. This **state-driven approach** ensures that the mobile interface remains synchronized with the user's navigation intent.

## Conditional Rendering of the Drawer Component

The drawer itself is rendered using a conditional block that checks the current state. Lines 29‑42 in [`src/App.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/App.tsx) contain the logic:

```tsx
{mobilePanel !== 'graph' && (
  <div className="mobile-panel-drawer">
    <button className="mobile-panel-close"
            onClick={() => setMobilePanel('graph')}>✕ Close</button>
    {mobilePanel === 'quests' && <QuestPanel />}
    {mobilePanel === 'inspector' && (
      <>
        <SearchFilter />
        <InspectorPanel />
      </>
    )}
    {mobilePanel === 'query' && <QueryPlayground />}
  </div>
)}

```

This pattern ensures that `QuestPanel`, `InspectorPanel`, or `QueryPlayground` only render when explicitly requested, keeping the DOM lightweight on desktop viewports where the drawer remains hidden.

### Rendering the Bottom Tab Bar

The navigation interface consists of a bottom tab bar defined at lines 13‑27 in [`src/App.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/App.tsx). The tabs update the `mobilePanel` state when clicked:

```tsx
<div className="mobile-panel-tabs">
  <button className={`mobile-tab ${mobilePanel === 'graph' ? 'active' : ''}`}
          onClick={() => setMobilePanel('graph')}>
    Graph
  </button>
  {/* Additional tabs for 'quests', 'inspector', 'query' */}
</div>

```

The tab bar renders continuously but remains hidden on wide screens via CSS, ensuring seamless transitions between mobile and desktop layouts.

## Responsive Visibility via CSS Media Queries

The actual toggling between desktop and mobile views happens in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css). Lines 4189‑4196 define a media query that activates the mobile interface:

```css
.mobile-panel-drawer { display: none; }

@media (max-width: 900px) {
  .mobile-panel-drawer {
    display: flex;
    flex-direction: column;
  }
  .mobile-panel-tabs { display: flex; flex-shrink: 0; }
}

```

By default, both `.mobile-panel-drawer` and `.mobile-panel-tabs` use `display: none`. The `@media (max-width: 900px)` rule overrides this to `display: flex`, ensuring these elements only appear on narrow viewports.

## Fixed Positioning and Layout Structure

Once visible, the drawer requires specific positioning to cover the main content without disrupting the underlying graph view. Lines 4197‑4204 in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css) establish:

```css
@media (max-width: 900px) {
  .mobile-panel-drawer {
    position: fixed;
    top: 56px;
    bottom: calc(48px + env(safe-area-inset-bottom, 0px));
    left: 0; right: 0;
    background: var(--bg-secondary);
    z-index: 500;
    overflow-y: auto;
    border-top: 1px solid var(--border-color);
  }
}

```

The **fixed positioning** spans from `top: 56px` to `bottom: calc(48px + env(safe-area-inset-bottom, 0px))`, accounting for both the header and safe area on mobile devices. The high `z-index: 500` ensures the drawer floats above interactive canvas elements.

### Flex Adjustments for Inner Panels

Inside the drawer, flex properties manage the internal layout. Lines 4226‑4232 configure the panel containers to fill available space while maintaining header integrity:

```css
.mobile-panel-drawer > * {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

```

These rules ensure that inner panels expand to fill the drawer while allowing content to scroll appropriately within the constrained viewport.

## Closing Drawers and Resetting State

User interaction for closing the drawer is handled by a button that resets the state. At lines 32‑33 in [`src/App.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/App.tsx), the close button calls `setMobilePanel('graph')`:

```tsx
<button className="mobile-panel-close"
        onClick={() => setMobilePanel('graph')}>✕ Close</button>

```

When the state returns to `'graph'`, the conditional rendering removes the drawer from the DOM, revealing the main graph view beneath without requiring a page reload.

## Summary

- The **React state** `mobilePanel` tracks which side panel is active, using a union type of `'graph' | 'quests' | 'inspector' | 'query'`.
- **Conditional rendering** in [`src/App.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/App.tsx) lines 29‑42 displays the drawer only when `mobilePanel !== 'graph'`.
- A **bottom tab bar** provides navigation on mobile devices, rendered at lines 13‑27 but hidden via CSS on wide screens.
- **CSS media queries** at `max-width: 900px` in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css) lines 4189‑4196 toggle `display: flex` to show mobile elements only on narrow viewports.
- **Fixed positioning** with `z-index: 500` ensures the drawer overlays the main content without layout shifts.
- The **close button** resets state to `'graph'`, removing the drawer and returning focus to the graph visualization.

## Frequently Asked Questions

### How does the Ontology Playground determine when to show the mobile drawer?

The application checks the `mobilePanel` React state variable. When this value is anything other than `'graph'`, the conditional rendering logic in [`src/App.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/App.tsx) lines 29‑42 inserts the `.mobile-panel-drawer` element into the DOM. CSS media queries then ensure this drawer is only visible on screens narrower than 900px.

### Why use a max-width of 900px for the mobile breakpoint?

The 900px breakpoint in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css) lines 4189‑4196 represents a threshold where the desktop sidebar layout becomes impractical. Below this width, the interface switches to a mobile-first approach with a bottom tab bar and full-screen drawer, optimizing touch interactions and readability on phones and narrow tablets.

### How is the drawer positioned to cover the graph without affecting layout?

The drawer uses `position: fixed` with coordinates spanning `top: 56px` to `bottom: calc(48px + env(safe-area-inset-bottom, 0px))` as defined in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css) lines 4197‑4204. This removes the element from the document flow and positions it absolutely relative to the viewport, overlaying the graph canvas with `z-index: 500` while maintaining proper spacing for device safe areas.

### What happens when a user clicks the close button on the mobile panel?

The close button triggers `setMobilePanel('graph')` at lines 32‑33 in [`src/App.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/App.tsx), resetting the state to the default graph view. This state change causes React to unmount the drawer component, immediately returning the user to the main graph interface without page reloads.