Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
03e86dd
Added demo
matthewlipski Aug 3, 2026
e45f5a1
Replaced demo toolbar with actual toolbar
matthewlipski Aug 6, 2026
d9902bf
Properly fixed dropdowns
matthewlipski Aug 7, 2026
af12646
- Fixed link button popover close dismissing virtual keyboard
matthewlipski Aug 7, 2026
1d4fbe3
Added transition to toolbar
matthewlipski Aug 12, 2026
07d9ff7
Replaced hook with pure CSS
matthewlipski Aug 13, 2026
faa72b5
Added docs
matthewlipski Aug 13, 2026
d070f14
Removed redundant `direction` prop
matthewlipski Aug 13, 2026
ece1f83
Merged main
matthewlipski Aug 13, 2026
e23b2f3
Refactored `useVirtualViewportRect`
matthewlipski Aug 14, 2026
57b68bf
Fixed some SSR issues
matthewlipski Aug 17, 2026
dfb89ac
Removed `portalRoot` prop
matthewlipski Aug 17, 2026
5a5fcd1
Merge branch 'main' into mobile-toolbar-demo
matthewlipski Aug 17, 2026
4b920a1
Implemented PR feedback
matthewlipski Aug 17, 2026
52933c2
Removed unused component
matthewlipski Aug 17, 2026
8a03207
Small fix
matthewlipski Aug 17, 2026
f779a4b
Increased mobile formatting toolbar tap target size
matthewlipski Aug 17, 2026
19428d4
- Fixed `getActiveStyles` with empty selections
matthewlipski Aug 20, 2026
a174fef
Refactored `FormattingToolbarController` to handle whether the deskto…
matthewlipski Aug 20, 2026
51aa828
Made mobile toolbar only appear when attached editor instance is focused
matthewlipski Aug 20, 2026
9c748cc
Fixed toolbar select desktop regression
matthewlipski Aug 20, 2026
3a95797
Increased required threshold to detect orientation change
matthewlipski Aug 20, 2026
69c6a71
Fixed formatting toolbar not opening when using touch-capable device …
matthewlipski Aug 20, 2026
a42e93c
Made mobile formatting toolbar center-aligned
matthewlipski Aug 20, 2026
1d0ef95
Small fix
matthewlipski Aug 20, 2026
e441113
docs: name the mobile toolbar layouts + add layout toggle to the exam…
YousefED Aug 20, 2026
09692f4
Merge remote-tracking branch 'origin/mobile-toolbar-demo' into mobile…
matthewlipski Aug 20, 2026
d4079d4
Made `.bn-scroll-host` class name apply all necessary styles automati…
matthewlipski Aug 20, 2026
b05f7ab
Added second editor to example
matthewlipski Aug 20, 2026
cef137d
Cached `isTouchDevice` result
matthewlipski Aug 20, 2026
be8838e
Cleaned up portal element context
matthewlipski Aug 21, 2026
51b89d0
Moved injected styles to stylesheet
matthewlipski Aug 21, 2026
83c2a00
Added `hide` middleware to `GenericPopover`
matthewlipski Aug 21, 2026
22c7645
Minor fixes
matthewlipski Aug 25, 2026
9a9f218
Fixed popovers
matthewlipski Aug 25, 2026
384ffd2
Implemented PR feedback
matthewlipski Aug 25, 2026
5696c41
Small fix
matthewlipski Aug 26, 2026
b54dcf5
Fixed Android gap
matthewlipski Aug 27, 2026
2656d73
Small fixes
matthewlipski Aug 28, 2026
0f80e42
Small docs change
matthewlipski Aug 28, 2026
852849f
Merge branch 'main' into mobile-toolbar-demo
YousefED Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion docs/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Footer } from "@/components/Footer";
import { Provider } from "@/components/provider";
import { getFullMetadata } from "@/lib/getFullMetadata";
import { Analytics } from "@vercel/analytics/next";
import { Metadata } from "next";
import { Metadata, Viewport } from "next";
import "./global.css";
import "./gradients.css";
import "./styles.css";
Expand All @@ -13,6 +13,16 @@ export const metadata: Metadata = getFullMetadata({
"A beautiful text editor that just works. Easily add an editor to your app that users will love. Customize it with your own functionality like custom blocks or AI tooling.",
});

// Resizes the layout viewport (not just the visual viewport) when a virtual
// keyboard opens, so `position: fixed` elements can be pinned to the top of the
// keyboard. Must be set in the initial HTML, so it lives here rather than in an
// example's App. Mirrors the examples' generated `index.html` viewport meta.
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
interactiveWidget: "resizes-content",
};

export default function Layout({ children }: LayoutProps<"/">) {
return (
<html lang="en" suppressHydrationWarning>
Expand Down
68 changes: 68 additions & 0 deletions docs/content/docs/react/components/formatting-toolbar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,71 @@ The first element in the default Formatting Toolbar is the Block Type Select, an
<Example name="ui-components/formatting-toolbar-block-type-items" />

Here, we use the `FormattingToolbar` component but keep the default buttons (we don't pass any children). Instead, we pass our customized Block Type Select items using the `blockTypeSelectItems` prop.

## Mobile Formatting Toolbar

On touch devices, BlockNote's default UI replaces the floating Formatting Toolbar with a mobile Formatting Toolbar that sits just above the on-screen keyboard. It shows the same items as the regular Formatting Toolbar and is enabled by default - there's nothing to set up. Open any of the examples above on a phone to see it.

### Viewport setup

For the best behavior, add `interactive-widget=resizes-content` to your page's viewport meta tag:

```html
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
```

The mobile Formatting Toolbar works with two page layouts. Which one you get depends on your app's layout:

- **Scrolling document** (the default): the page scrolls as usual and BlockNote repositions the toolbar as you scroll.
- **Scroll container**: the document itself doesn't scroll; a container pinned to the visual viewport scrolls instead, and the toolbar never has to move.

### Scrolling document

This is what you get without any changes to your app. The toolbar follows the visible area above the keyboard as the page scrolls. On iOS, mobile browsers only report visual viewport changes after the fact, so the toolbar can lag or jitter slightly while the page is scrolling. If that matters for your app, switch to a scroll container.

### Scroll container

In this layout, `<html>` and `<body>` are locked and all page content lives inside a single scroll container that BlockNote keeps aligned with the visual viewport. Since the document never scrolls, the toolbar can stay at a truly fixed position and the lag/jitter disappears. This comes with some potential trade-offs though. Browser gestures that rely on document scrolling, like pull-to-refresh, may stop working and browser UI elements like the address bar, which normally hides and reappears as you scroll, may stay fixed. Note that these trade-offs are browser-dependent - some will have neither, while others will have both.

To set this up, add the `bn-scroll-container` class to the element that wraps all your scrollable page content:

```tsx
<div className="bn-scroll-container">{/* nav, editor, page content... */}</div>
```

<Callout type="warning">
Your app should only ever have a single `bn-scroll-container` element. It's
pinned to the visual viewport with `position: fixed`, so multiple containers
would overlap each other. Wrap all your scrollable page content in one.
</Callout>

That's all the setup needed. These styles ship in BlockNote's stylesheet:

```css
html:has(.bn-scroll-container),
body:has(.bn-scroll-container) {
overflow: hidden;
}
```

This locks scrolling on `<html>` and `<body>` whenever a `bn-scroll-container` element is present. The container is then pinned to the visual viewport using the `--bn-vv-*` CSS variables, which BlockNote sets on `<html>` at runtime as the viewport changes:

```css
.bn-scroll-container {
position: fixed;
top: var(--bn-vv-top, 0px);
left: var(--bn-vv-left, 0px);
width: var(--bn-vv-width, 100vw);
height: var(--bn-vv-height, 100dvh);
overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
}
```

These variables track the [visual viewport](https://developer.mozilla.org/en-US/docs/Web/API/VisualViewport) - the part of the page actually visible above the keyboard. BlockNote keeps `--bn-vv-top`, `--bn-vv-left`, `--bn-vv-width`, and `--bn-vv-height` (plus `--bn-vv-scale`, the pinch-zoom factor) up to date as the keyboard opens and closes and as the user pans or zooms, so the scroll container always lines up with the visible area above the keyboard without any JavaScript on your end.

Because this layout changes how the whole page scrolls, the example can't be embedded here - open the [standalone example](https://playground.blocknotejs.org/ui-components/mobile-formatting-toolbar?hideMenu=true) on a phone instead. It puts a navigation bar, some static text, and the editor inside an element with the `bn-scroll-container` class, and the switch in the navigation bar toggles the pinned scroll container layout on and off so you can compare it with the default scrolling document. Select some text and scroll in each layout to see the difference.
5 changes: 4 additions & 1 deletion examples/01-basic/01-minimal/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Basic Setup</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/02-block-objects/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Displaying Document JSON</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/03-multi-column/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Multi-Column Blocks</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/04-default-blocks/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Default Schema Showcase</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/05-removing-default-blocks/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Removing Default Blocks from Schema</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/06-block-manipulation/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Manipulating Blocks</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/07-selection-blocks/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Displaying Selected Blocks</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/08-ariakit/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Use with Ariakit</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/09-shadcn/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Use with ShadCN</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/10-localization/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Localization (i18n)</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/11-custom-placeholder/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Change placeholder text</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/12-multi-editor/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Multi-Editor Setup</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/13-custom-paste-handler/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Custom Paste Handler</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/14-editor-scrollable/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Scrollable Editor</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/15-shadowdom/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Shadow DOM</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/16-read-only-editor/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Read-only Editor</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/17-no-trailing-block/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>No Trailing Block</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/01-basic/testing/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Test Editor</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/02-backend/01-file-uploading/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Upload Files</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/02-backend/02-saving-loading/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Saving &amp; Loading</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/02-backend/03-s3/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Upload Files to AWS S3</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/02-backend/04-rendering-static-documents/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Rendering static documents</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
5 changes: 4 additions & 1 deletion examples/03-ui-components/01-ui-elements-remove/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Removing UI Elements</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Adding Formatting Toolbar Buttons</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Adding Block Type Select Items</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
Expand Down
Loading
Loading