<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Huy Phan</title>
    <description>The latest articles on DEV Community by Huy Phan (@huy_phan_173c933272803418).</description>
    <link>https://dev.to/huy_phan_173c933272803418</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4201866%2F2bdac583-b8f3-483b-b3a4-717fb68cfdb7.png</url>
      <title>DEV Community: Huy Phan</title>
      <link>https://dev.to/huy_phan_173c933272803418</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/huy_phan_173c933272803418"/>
    <language>en</language>
    <item>
      <title>How I built Word-style A4 pages for TipTap without touching the document</title>
      <dc:creator>Huy Phan</dc:creator>
      <pubDate>Fri, 23 Oct 2026 08:07:55 +0000</pubDate>
      <link>https://dev.to/huy_phan_173c933272803418/how-i-built-word-style-a4-pages-for-tiptap-without-touching-the-document-778</link>
      <guid>https://dev.to/huy_phan_173c933272803418/how-i-built-word-style-a4-pages-for-tiptap-without-touching-the-document-778</guid>
      <description>&lt;p&gt;If you've built an editor with &lt;strong&gt;TipTap / ProseMirror&lt;/strong&gt;, you've probably heard this request: &lt;em&gt;"Make it look like Word. Show real A4 pages, and print exactly what I see."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sounds simple, but a &lt;code&gt;contenteditable&lt;/code&gt; is one endless column of content. It has no idea what a page is.&lt;/p&gt;

&lt;p&gt;This post walks through how I added A4 pagination to my open-source editor, &lt;a href="https://github.com/HuyPhan1232203/Text-editor-react" rel="noopener noreferrer"&gt;Text Editor React&lt;/a&gt;, and packaged it as a free extension: &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/tiptap-a4-pagination" rel="noopener noreferrer"&gt;&lt;code&gt;tiptap-a4-pagination&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://text-editor-react-neon.vercel.app/" rel="noopener noreferrer"&gt;https://text-editor-react-neon.vercel.app/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The options I found
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Tiptap Pages&lt;/strong&gt; (official)&lt;/td&gt;
&lt;td&gt;Paid Pro extension, private npm registry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community extensions&lt;/td&gt;
&lt;td&gt;Inject global CSS (change table &lt;code&gt;display&lt;/code&gt;, &lt;code&gt;word-break: break-all&lt;/code&gt;, force padding/width on the editor), which broke my existing styles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One editor per page&lt;/td&gt;
&lt;td&gt;Cursor movement, undo and selections across pages become a nightmare&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I wanted one thing: &lt;strong&gt;don't modify the document, don't touch the user's CSS&lt;/strong&gt;. &lt;code&gt;editor.getHTML()&lt;/code&gt; should stay exactly as clean as before.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: spacers as decorations
&lt;/h2&gt;

&lt;p&gt;ProseMirror has &lt;strong&gt;decorations&lt;/strong&gt;: elements that are rendered but are not part of the document. So:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Measure every top-level block (paragraphs, headings, tables, images…).&lt;/li&gt;
&lt;li&gt;When a block would cross the bottom of a page, insert a &lt;strong&gt;widget decoration&lt;/strong&gt; right before it.&lt;/li&gt;
&lt;li&gt;Make the widget exactly as tall as &lt;em&gt;the rest of the page + bottom margin + the gray gap + the next page's top margin&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The block lands at the top of the next sheet, and visually you get separate pages. The document doesn't change by a single byte.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Decoration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createSpacer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageBreak&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;side&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ignoreSelection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The layout is recomputed whenever the editor's height changes (typing, images loading, fonts arriving) with a &lt;code&gt;ResizeObserver&lt;/code&gt; and &lt;code&gt;requestAnimationFrame&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't trust the current positions
&lt;/h2&gt;

&lt;p&gt;My first attempt used each block's &lt;code&gt;getBoundingClientRect().top&lt;/code&gt;. The problem: &lt;strong&gt;the spacers I had already inserted shift those positions&lt;/strong&gt;. Measure again, get a different answer, move the spacers, measure again… an endless loop.&lt;/p&gt;

&lt;p&gt;What works is to &lt;strong&gt;rebuild the layout from heights and margins only&lt;/strong&gt;, as if there were no spacers at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;   &lt;span class="c1"&gt;// bottom of the previous block&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pageEnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentHeight&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Adjacent vertical margins collapse, like in CSS&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevMarginBottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;marginTop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pageEnd&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pageStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Spacer before this block: it starts at the top of the next page&lt;/span&gt;
    &lt;span class="nx"&gt;breaks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nextPageTop&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;marginTop&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;prevMarginBottom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;pageEnd&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;pitch&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bottom&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function is &lt;strong&gt;pure&lt;/strong&gt;: blocks in, page breaks out, no DOM. That made it easy to unit test, and the package has 13 tests for this part alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The traps I fell into
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Margin collapse
&lt;/h3&gt;

&lt;p&gt;Once a spacer sits between two paragraphs, their margins &lt;strong&gt;stop collapsing&lt;/strong&gt;, and everything is off by a few pixels. On top of that, the first block's top margin "leaks" out of the editor, which shifted my origin by 16px. The fix was to measure from the top of the page's content area (the page's padding-top), not from the editor element.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Floated images
&lt;/h3&gt;

&lt;p&gt;An image with &lt;code&gt;float: left&lt;/code&gt; (text wrapping) doesn't push the next block down: the next paragraph sits &lt;em&gt;beside&lt;/em&gt; it. The layout has to skip a floated block's height when placing the following block.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Splitting tables across pages
&lt;/h3&gt;

&lt;p&gt;Pushing a long table to the next page as a whole looks terrible. Instead, inside a table the spacer is &lt;strong&gt;a &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt;&lt;/strong&gt;: a widget decoration placed between two row nodes.&lt;/p&gt;

&lt;p&gt;The biggest bug here: I gave the spacer cell &lt;code&gt;colSpan = 1000&lt;/code&gt;, "to be safe". With &lt;code&gt;table-layout: fixed&lt;/code&gt;, the browser &lt;strong&gt;created 1000 phantom columns&lt;/strong&gt; and squeezed the real ones to almost nothing, so text stacked one character per line. The fix: use the real column count, &lt;code&gt;TableMap.get(table).width&lt;/code&gt; from &lt;code&gt;prosemirror-tables&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Rowspan
&lt;/h3&gt;

&lt;p&gt;Inserting a spacer row in the middle of a cell with &lt;code&gt;rowspan&lt;/code&gt; breaks the table instantly. Rows joined by a rowspan are grouped into &lt;strong&gt;one unbreakable group&lt;/strong&gt;, and page breaks only go between groups.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Repeating the header row
&lt;/h3&gt;

&lt;p&gt;Like Word, when a table continues on the next page, its header row shows again. I copy the first row's DOM (with the measured width of each cell, so the columns line up) into the end of the spacer row.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Zoom
&lt;/h3&gt;

&lt;p&gt;Later I added a Google Docs–style zoom (50–200%) with a CSS &lt;code&gt;transform: scale()&lt;/code&gt; on the page. Every &lt;code&gt;getBoundingClientRect()&lt;/code&gt; suddenly returned scaled values, and page breaks moved around with the zoom level. Now the extension divides every measurement by the page's on-screen scale:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;elementScale&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetWidth&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;offsetWidth&lt;/code&gt; ignores transforms, so the ratio is the zoom. Page breaks are now identical at every level.&lt;/p&gt;

&lt;h2&gt;
  
  
  PDF export without a server
&lt;/h2&gt;

&lt;p&gt;The project used to export PDFs through a Puppeteer backend. Now it &lt;strong&gt;prints through the browser&lt;/strong&gt; (&lt;code&gt;window.print()&lt;/code&gt; on a hidden iframe containing only the pages):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@page { size: A4; margin: ... }&lt;/code&gt; comes from the page settings.&lt;/li&gt;
&lt;li&gt;Spacers are removed and &lt;code&gt;break-before: page&lt;/code&gt; goes on the block right after each one, so the PDF breaks &lt;strong&gt;exactly&lt;/strong&gt; where the editor does.&lt;/li&gt;
&lt;li&gt;Table header rows move into a &lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt;, and Chrome repeats them on every printed page.&lt;/li&gt;
&lt;li&gt;Headers, footers and page numbers use &lt;strong&gt;CSS page margin boxes&lt;/strong&gt; (Chrome 131+):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@page&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;@top-left&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Quarterly report"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;@bottom-right&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Page "&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s1"&gt;" of "&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use it in your project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;tiptap-a4-pagination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pagination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createPageLayout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tiptap-a4-pagination&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tiptap-a4-pagination/styles.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Margins in mm&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPageLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;marginTop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;25.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;marginBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;25.4&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useEditor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;StarterKit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Pagination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;pageSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPageLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;About 5 KB gzipped, TipTap 3 only. Full docs are in the &lt;a href="https://github.com/HuyPhan1232203/Text-editor-react/tree/main/packages/tiptap-a4-pagination" rel="noopener noreferrer"&gt;package README&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A single paragraph taller than the space left on a page moves to the next page as a whole; paragraphs are not split across two pages yet.&lt;/li&gt;
&lt;li&gt;A table row taller than a whole page overflows into the gap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What else is in the editor
&lt;/h2&gt;

&lt;p&gt;Comments in the margin, link sharing with real-time editing (peer-to-peer over WebRTC with Yjs, no backend), headers and footers, opening &lt;code&gt;.docx&lt;/code&gt; files, Google Docs paste that keeps checklists and tables, slash commands, find &amp;amp; replace, and zoom. All MIT.&lt;/p&gt;




&lt;p&gt;I'd love feedback, especially documents that break the layout. If you find it useful, a ⭐ on GitHub would mean a lot 🙏&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/HuyPhan1232203/Text-editor-react" rel="noopener noreferrer"&gt;https://github.com/HuyPhan1232203/Text-editor-react&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/tiptap-a4-pagination" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/tiptap-a4-pagination&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo: &lt;a href="https://text-editor-react-neon.vercel.app/" rel="noopener noreferrer"&gt;https://text-editor-react-neon.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
