Hands-On Project: Create a Tiny Notepad with Table Support Using HTML & JS
Hands‑on guide to building a tiny cross‑platform notepad with table support using contenteditable, JS, and modern browser APIs (2026).
Build a Tiny Cross‑Platform Notepad with Table Support — A Hands‑On Walkthrough
Hook: If you’re tired of fragmented learning resources and want a practical, project-driven way to learn modern web APIs, this walkthrough shows you how to build a lightweight, cross‑platform text editor that supports inserting and editing tables — like the Notepad tables feature Microsoft rolled out to Windows 11 users in late 2025 — but designed as an educational project for students, teachers, and lifelong learners.
Quick overview — What you’ll build and why it matters (inverted pyramid)
In this article you’ll get a complete, hands‑on project: a tiny notepad built with HTML and JavaScript using a contenteditable editor that supports inserting tables, editing cells, adding/removing rows and columns, and saving to disk or local storage. You’ll learn practical techniques for UX, accessibility, offline persistence, and modern browser APIs relevant in 2026 — like the File System Access API and the Async Clipboard API — all while keeping the app lightweight and framework‑free.
Why build this now? Trends and context for 2026
Recent years have pushed simple, focused editors back into the spotlight. In late 2025 Microsoft added native table support to Windows 11 Notepad, acknowledging that even lightweight editors need structured content tools. That reflects a broader 2024–2026 trend: users expect small apps to behave like full editors when they need to — tables, rich paste, keyboard shortcuts — but still remain fast and privacy‑friendly.
From a developer perspective, browsers now offer robust APIs (File System Access, improved clipboard controls, container queries, and performant IndexedDB usage patterns). Building a tiny notepad teaches you how to combine these capabilities and ship a usable cross‑platform experience without Electron bloat.
Project goals and constraints
- Essential features: plain text editing, insert table, edit table cells, add/remove rows/columns, delete table.
- Persistence: save/load via localStorage or IndexedDB, and optional File System Access API when available.
- Cross‑platform UX: keyboard shortcuts, touch support, ARIA labels for accessibility.
- Keep it minimal: no large frameworks — pure JavaScript and semantic HTML.
Project structure
Use a simple layout. This helps learners focus on features instead of build tooling.
index.html
styles.css
app.js
HTML skeleton
The editor uses a lightweight toolbar and a single div[contenteditable] for the document. Tables are regular DOM elements so the user can select and edit cells naturally.
<!-- index.html (excerpt) -->
<div class="toolbar" role="toolbar" aria-label="Editor toolbar">
<button id="btn-insert-table" title="Insert Table (Ctrl+T)">Insert Table</button>
<button id="btn-save">Save</button>
<button id="btn-open">Open</button>
<button id="btn-export-html">Export HTML</button>
</div>
<div id="editor" contenteditable="true" role="article" aria-label="Text editor" spellcheck="true">
<p>Start typing...</p>
</div>
<dialog id="tableDialog">
<form method="dialog">
<label>Rows: <input type="number" id="tableRows" min="1" value="2"/></label>
<label>Cols: <input type="number" id="tableCols" min="1" value="3"/></label>
<menu><button id="insertTableOk">Insert</button></menu>
</form>
</dialog>
CSS: lightweight UX
Keep styles minimal but clear. Use CSS custom properties so learners can tweak quickly.
/* styles.css (excerpt) */
:root{--bg:#fff;--fg:#111;--muted:#666;--gap:10px}
body{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial; background:var(--bg); color:var(--fg); padding:16px}
.toolbar{display:flex;gap:8px;margin-bottom:8px}
#editor{min-height:320px;border:1px solid #ddd;padding:12px;border-radius:6px;white-space:pre-wrap}
#editor table{border-collapse:collapse;width:100%}
#editor td, #editor th{border:1px solid #cfcfcf;padding:6px}
#editor td:focus{outline:2px solid #4a90e2}
Core JavaScript — insert and edit tables
This section contains the JS that matters. We’ll implement: insertTable, a small API for modifying rows/cols, keyboard shortcuts, and persistence hooks.
Insert table function
Insert a semantic table element at the current caret position. We create the table and insert editable cells (each cell keeps contenteditable implicitly because the container is contenteditable).
// app.js (excerpt)
function createTableHtml(rows, cols) {
const table = document.createElement('table');
const tbody = document.createElement('tbody');
for (let r = 0; r < rows; r++) {
const tr = document.createElement('tr');
for (let c = 0; c < cols; c++) {
const td = document.createElement('td');
td.textContent = '';
td.setAttribute('contenteditable', 'true');
td.setAttribute('role', 'gridcell');
tr.appendChild(td);
}
tbody.appendChild(tr);
}
table.appendChild(tbody);
table.setAttribute('role','grid');
table.dataset.tinyNote = 'true';
return table;
}
function insertTableAtCaret(rows, cols) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount) return;
const range = sel.getRangeAt(0);
const table = createTableHtml(rows, cols);
range.deleteContents();
range.insertNode(table);
// Move caret into first cell
const firstCell = table.querySelector('td');
placeCaretIn(firstCell);
}
function placeCaretIn(node) {
const range = document.createRange();
range.selectNodeContents(node);
range.collapse(true);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
Add/remove row & column helpers
We use event delegation: the toolbar buttons for row/col manipulation will operate on the closest table for the current selection.
function getActiveTableFromSelection() {
const sel = window.getSelection();
if (!sel.rangeCount) return null;
let node = sel.anchorNode;
while (node && node.nodeType === Node.TEXT_NODE) node = node.parentNode;
return node ? node.closest('table[data-tinynote]') : null;
}
function addRow(table, index = -1) {
const cols = table.querySelector('tr').children.length;
const tr = document.createElement('tr');
for (let i = 0; i < cols; i++) {
const td = document.createElement('td');
td.setAttribute('contenteditable','true');
td.textContent = '';
tr.appendChild(td);
}
if (index < 0) table.tBodies[0].appendChild(tr);
else table.tBodies[0].insertBefore(tr, table.tBodies[0].rows[index]);
}
function addColumn(table, index = -1) {
const rows = table.rows;
for (let r = 0; r < rows.length; r++) {
const cell = document.createElement('td');
cell.setAttribute('contenteditable','true');
cell.textContent = '';
if (index < 0) rows[r].appendChild(cell);
else rows[r].insertBefore(cell, rows[r].cells[index]);
}
}
function removeRow(table, index) {
table.deleteRow(index);
}
function removeColumn(table, index) {
const rows = table.rows;
for (let r = 0; r < rows.length; r++) rows[r].deleteCell(index);
}
Keyboard shortcuts & UX
Keyboard-driven editors feel professional. We'll wire Ctrl+T to open insert table, Ctrl+S to save (prevent default), and Esc to close dialogs.
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 't') {
e.preventDefault();
document.getElementById('tableDialog').showModal();
}
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
e.preventDefault();
saveDocument();
}
});
Persistence: localStorage, IndexedDB, and File System Access API
Saving is essential. For a tiny app, start with localStorage or IndexedDB. For a desktop‑like experience, use the File System Access API when available (Chromium family). As of early 2026, support is broad in Chromium browsers and available in PWA contexts, making it a practical enhancement.
Simple local save/load
function saveToLocal() {
localStorage.setItem('tinynote:content', document.getElementById('editor').innerHTML);
}
function loadFromLocal() {
const data = localStorage.getItem('tinynote:content');
if (data) document.getElementById('editor').innerHTML = data;
}
Optional File System Access (save as .html)
async function saveToFile() {
if (!window.showSaveFilePicker) return saveToLocal();
const opts = {types:[{description:'HTML file',accept:{'text/html':['.html']}}]};
const handle = await window.showSaveFilePicker(opts);
const writable = await handle.createWritable();
await writable.write(`<!doctype html><meta charset="utf-8"><body>${document.getElementById('editor').innerHTML}</body>`);
await writable.close();
}
Clipboard, paste, and tables: practical tips
Clipboard behavior is a big UX concern. In 2026, the Async Clipboard API is stable across modern browsers. For our notepad:
- When pasting, prefer HTML if the user pastes a table, otherwise strip styles for cleanliness.
- Normalize pasted HTML to avoid injected layout or script elements.
- Provide a "Paste as plain text" command for students and teachers to prevent format surprises during copy/paste from web pages or Word documents.
document.getElementById('editor').addEventListener('paste', (e) => {
e.preventDefault();
const html = (e.clipboardData || window.clipboardData).getData('text/html');
const text = (e.clipboardData || window.clipboardData).getData('text/plain');
if (html) {
// Minimal sanitization: drop scripts, inline styles
const sanitized = sanitizeHtml(html);
document.execCommand('insertHTML', false, sanitized);
} else {
document.execCommand('insertText', false, text);
}
});
function sanitizeHtml(input) {
// Very small sanitizer: allow only table, tr, td, th, p, br, strong, em
const template = document.createElement('template');
template.innerHTML = input;
const whitelist = new Set(['TABLE','TBODY','TR','TD','TH','P','BR','STRONG','EM']);
function clean(node) {
const children = Array.from(node.childNodes);
for (const child of children) {
if (child.nodeType === Node.ELEMENT_NODE) {
if (!whitelist.has(child.nodeName)) {
// unwrap: move children up
while (child.firstChild) node.insertBefore(child.firstChild, child);
node.removeChild(child);
} else {
// remove attributes
for (const attr of Array.from(child.attributes)) child.removeAttribute(attr.name);
clean(child);
}
}
}
}
clean(template.content);
return template.innerHTML;
}
Accessibility & UX details
A lightweight editor still needs good accessibility. Some practical rules:
- Use ARIA roles: editor (article), toolbar (toolbar), tables (grid/gridcell).
- Ensure all toolbar buttons have meaningful
titleand visible labels. - Keyboard navigation inside cells: arrow keys should move caret by default because cells are contenteditable. For advanced behavior implement custom grid navigation only if necessary.
- Offer high‑contrast CSS and large font modes via simple toggles for classroom settings.
Performance considerations
Even tiny editors can become sluggish with large documents. Practical tips:
- Avoid storing huge blobs in localStorage; use IndexedDB for large content.
- Debounce autosave (e.g., 1–2s after last keystroke).
- Limit complex DOM operations; when modifying tables, prefer fragment updates.
Extending the project (learning exercises)
Turn this base into learning tasks for students or self‑study exercises:
- Implement merge/unmerge cell support (use colspan/rowspan and careful normalization).
- Add export to Markdown with table conversion (practice string parsing).
- Pack as a PWA so the editor can be installed — practice manifest and service worker basics.
- Integrate a minimal LLM assistant to suggest table headers or auto‑format pasted CSV (be mindful of privacy and API keys).
Real‑world classroom case study
At a community college workshop in early 2026, instructors used a tiny notepad like this to teach data collection. Students could paste CSV from a survey and quickly convert the data into a table for class analysis. The small app removed barriers: no installs, instant feedback, and the ability to save locally with the File System Access API when needed.
"Students loved that the editor stayed lightweight yet behaved like a real tool — tables, keyboard shortcuts, and reliable saving. It made the leap from toy project to classroom utility." — Workshop instructor
Testing and deployment
Testing checklist:
- Cross‑browser: Chrome, Edge, Firefox, and Safari (test File System API fallbacks).
- Accessibility: test with screen readers and keyboard only navigation.
- Mobile/touch: verify table insertion and cell focus behaviors on touch devices.
For distribution consider a simple static host (GitHub Pages) or package as a PWA. If you want native packaging later, choose Tauri for a small binary or a light Electron wrapper for feature parity across platforms.
Security and privacy
Keep these points front of mind — especially in educational settings:
- Never upload student content to third‑party servers without consent.
- If adding cloud sync, encrypt before sending or use user‑controlled keys.
- Avoid executing pasted HTML or scripts; sanitize inputs as shown above.
Advanced strategies & 2026 predictions
Looking ahead, tiny editors will increasingly adopt assistive AI features (summarization, table auto-structure), but local, privacy‑first processing will grow in importance. Browser vendors will continue to improve file and clipboard APIs, making desktop‑grade behaviors possible without heavyweight frameworks. For learners, that means more opportunities to build production‑ready web apps using vanilla JS and standard web APIs.
Actionable checklist — What to implement next (takeaways)
- Implement the basic HTML/CSS/JS skeleton and contenteditable editor.
- Add insertTableAtCaret(rows, cols) and table manipulation helpers.
- Wire keyboard shortcuts (Ctrl+T, Ctrl+S) and basic persistence.
- Sanitize pasted HTML and support paste‑as‑plain‑text.
- Test across browsers and add File System Access fallback behavior.
Summary
By building this tiny notepad you learn practical use of contenteditable, DOM manipulation for tables, modern browser APIs (File System Access, Async Clipboard), and UX considerations for classroom and cross‑platform use. The project mirrors the functionality Microsoft added to Notepad in late 2025, but keeps the code approachable and extendable for learners in 2026.
Next steps & call to action
Ready to try it? Fork the starter repo, implement one extension (merge cells or Markdown export), and share your version with a short writeup of what you learned. If you want, post your questions or solutions in the comments or in our community forum — I’ll review notable forks and highlight great student projects.
Build it, iterate, teach it: the best way to learn web dev is by shipping real features. Start with the minimal editor above, then add one advanced feature per week. Happy coding!
Related Reading
- Garden-Friendly Smart Home Controllers: Is a Home Mini or Dedicated Hub Better?
- Inspecting a Prefab: Home Inspection Checklist for Modern Manufactured Properties
- Why the New Filoni-Era Star Wars Slate Matters for Storytelling Students
- From Inbox to QPU Queue: Automating Job Submission via Gmail AI Extensions
- How Smart Lamps Can Improve Your Lingerie Care Routine (and How Not to Damage Fabrics)
Related Topics
Unknown
Contributor
Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.
Up Next
More stories handpicked for you
Navigating Content Creation in 2026: The Role of AI-Driven Insights
The Future of iOS: What Developers Need to Know About iOS 27
Remastering Classics: How to Bring Old Games to Life on New Platforms
Harnessing the Power of AI in Micro-Apps for Rapid Prototyping
Exploring New Developer Roles: How AI is Reshaping the Job Market
From Our Network
Trending stories across our publication group