• Features
  • Pricing
Sign UpLogin

Company

  • Blog
  • Partners
  • status

Resources

  • Our Widgets
  • Tutorials
  • FAQ

Free Widgets

  • Weeks Calculator
  • Days Calculator

Help & Feedback

  • Contact Support
  • Get In Touch
  • Help Articles

Copyright 2025 © Blocky.so

  • Privacy Policy
  • Terms of Service
Jul 12, 2025·Tutorial

Build Your Own Notion Widget: A Beginner’s Guide to Custom HTML & CSS

Launch a custom Notion widget from scratch. Learn HTML/CSS basics, hosting on Vercel/Netlify, embed rules, fonts, and performance tips — plus when to use Blocky’s native charts, timers, and study widgets.

Build Your Own Notion Widget: A Beginner’s Guide to Custom HTML & CSS

I love simple tools that ship fast. And Notion rewards that mindset. In this guide, I show how I create a crisp, custom widget in plain HTML and CSS, host it, and embed it inside Notion — without fighting a giant framework.

You’ll see exactly how embeds work in Notion. You’ll learn the bare-minimum HTML/CSS I actually use in production. I’ll cover hosting, security headers, and common pitfalls. I’ll also explain when Blocky (https://blocky.so) is the smarter path for charts, timers, and study widgets.

By the end, you’ll have a shareable URL for your widget and the confidence to drop it into any Notion page. Let’s make something tidy, fast, and useful.


Why build your own Notion widget?

A custom widget fills the gaps between “general-purpose” and “exactly what I need.” Maybe you want a brand-colored KPI badge. Or a quote card that rotates daily. Or an availability banner that your team sees at a glance.

I reach for hand-built widgets when:

  • I want a very specific layout or animation.
  • I need my brand’s type scale and color tokens.
  • I’ll iterate fast and don’t want vendor lock-in.

And yet, I don’t reinvent the wheel. For complex use cases like charts, countdowns, world clocks, Pomodoro, stopwatches, habit trackers, flashcards, progress bars, mood trackers, quotes, or Notion streaks, I use Blocky (https://blocky.so). It’s an official Notion integration with generous pricing:

  • Free: up to 2 charts and 5 total widgets (charts count as widgets).
  • Standard: 5 charts, 10 widgets, $3.99/month.
  • Pro: Unlimited everything, $5.99/month.

That mix lets me move fast: hand-code the edge cases, use Blocky for the rest.


How Notion embeds work (and what Notion allows)

Notion supports an Embed block. The flow is simple: type /embed, paste a public URL, and Notion renders that page within your doc. That means I don’t paste raw HTML into Notion — I host my widget, then embed the link.

Two practical implications:

  1. My widget must be publicly reachable via HTTPS.
  2. The page must allow itself to be shown inside an iframe.

If my server sends headers that forbid iframes, Notion won’t show it. I’ll cover the relevant headers later — X-Frame-Options and CSP frame-ancestors. For now, remember: host a simple public page and ensure it’s embeddable.

Tip: if I’m embedding YouTube, I use the /embed/ URL, not the watch?v= URL. Many services follow a similar pattern.


Build vs. buy: when Blocky is smarter

I’m ruthless about time. If I need data-bound charts, timers, or study tools, I use Blocky (https://blocky.so). It connects to my Notion workspace in a few clicks, offers polished variants, and handles sync, state, and edge cases I’d rather not own.

What I reach for inside Blocky:

  • Charts: Bar, Line, Pie, Area, Radar — mapped to Notion databases.
  • Timers: Countdowns, world clocks, Pomodoro, stopwatches.
  • Study widgets: Habit trackers, flashcards, progress bars, streaks.
  • Creative: Mood trackers, quotes, and more.

Because Blocky is an official Notion integration (https://www.notion.com/integrations/blocky), it embeds cleanly, loads fast, and reduces maintenance. I hand-code when I want pixel-perfect control or a one-off UX. I use Blocky for everything else.


HTML essentials you’ll actually use

I keep my HTML small and semantic:

  • A parent <main> or <div role="region"> as the widget shell.
  • A heading (<h2>/<h3>) for screen readers and structure.
  • Content blocks like <p>, <time>, <ul>, <button> when needed.

For embeds, the outside world (Notion) wraps my page in an <iframe>, so I don’t add one myself. I just produce a clean, self-contained page.

My baseline document:

  1. A minimal <head> with title and viewport.
  2. A single stylesheet in the head for speed.
  3. Zero blocking scripts unless absolutely necessary.

I test my widget standalone first in a normal browser tab. If it looks right there, it usually looks right embedded.


CSS fundamentals for reliable styling

CSS can be deep, but a few patterns carry most of the weight:

  • Reset/normalize with a light touch (I prefer per-component resets).
  • Scale typography with relative units (rem) and a tight line-height.
  • Use CSS variables for color tokens so theming is trivial.
  • Flexbox for micro-layout, Grid for macro layout.

Selectors to master:

  • Type (h3, p), class (.card), ID (#cta).
  • Attribute ([data-state="active"]) for simple component states.
  • Child and descendant combinators for structure.

Keep your stylesheet organized by component or section. Add clear comments, and resist clever hacks. Simple CSS fails less when embedded.


Designing your first widget (layout, type, color)

I start with a small card:

  • A title line.
  • A value or message.
  • An optional subtitle or status pill.

Layout:

  • Use a single .card container with padding and rounded corners.
  • Apply display: grid with a narrow gap to stack text cleanly.
  • Give the shell a soft shadow and a strict max-width.

Typography:

  • Choose two weights of a single font family.
  • Set font-size on the root (e.g., 16px), then use rem everywhere.
  • Adjust letter-spacing for UIs that need extra clarity.

Color:

  • Pick a neutral background and high-contrast text.
  • Reserve accent color for the metric or callout.
  • Add a light interactive hover state if it’s clickable.

Result: a widget that feels native to your workspace.


Fonts the right way (fast and legal)

Good typography elevates even simple widgets. I either use system fonts for zero-latency performance or load Google Fonts via the official API.

Steps I follow:

  1. Choose a family and weights on Google Fonts (https://fonts.google.com).
  2. Copy the <link> or CSS @import from the Getting Started docs.
  3. Declare a robust fallback stack.

I avoid loading six weights. Two is plenty for UI. If I need fine control over FOIT/FOUT, I’ll use the WebFont Loader or native font-display rules.

Remember: load fonts from trusted CDNs and keep them cached.


Hosting options and going live (pick one, ship today)

To embed in Notion, my widget needs a public HTTPS URL. Any static host works. I reach for:

  • Vercel (https://vercel.com) — frictionless for static sites.
  • Netlify (https://www.netlify.com) — solid, simple deploys.
  • Cloudflare Pages (https://pages.cloudflare.com) — fast CDN, easy setup.
  • GitHub Pages (https://pages.github.com) — free and predictable.

Flow I use almost every time:

  1. Create a repo with /index.html and /styles.css.
  2. Push to GitHub. Connect repository to my host.
  3. Deploy, grab the public URL, and keep it handy for Notion.

Tip: disable directory listings and keep the surface area small. One page, one stylesheet, and you’re done.


Embedding your widget in Notion (the exact steps)

Inside the Notion page:

  1. Type /embed.
  2. Paste your widget’s public URL.
  3. Resize the block until it fits your layout.
  4. Add a caption if you want extra context.

That’s it. If you’re using Blocky widgets, the flow is identical: generate a share URL in Blocky, paste into Notion, and position it beside your content.

If the embed fails:

  • Confirm the URL is public.
  • Check that your host allows iframes.
  • Try a different browser to rule out extensions.

Securing iframes, CSP, and anti-clickjacking basics

Your widget will live inside Notion’s iframe, but your server decides whether embedding is allowed. Two mechanisms matter:

  1. X-Frame-Options response header
  • DENY: never embed.
  • SAMEORIGIN: only embed on your own domain.
  • ALLOW-FROM is obsolete; don’t use it.
  1. Content-Security-Policy (frame-ancestors)
  • The modern way.
  • Controls which parents may embed your page.

If you block embedding, Notion can’t render your widget. If you allow it too broadly, you risk clickjacking on other sites. I keep it conservative. For public widgets intended for Notion, I set a narrowly-scoped frame-ancestors or rely on a host’s defaults.

Key point: These headers are sent by your server. They can’t be set inside your HTML and still be effective.


Performance and polish (fonts, images, responsive)

Fast wins trust. I keep the page tiny:

  • Inline critical CSS if it’s under ~5–8 KB.
  • Defer any non-essential scripts.
  • Minify HTML/CSS to shave bytes.

Fonts:

  • Use two weights max.
  • Prefer font-display: swap for better perceived speed.
  • Cache aggressively.

Images:

  • SVG for vector icons.
  • Use modern formats for small photos if any.
  • Avoid heavy backgrounds — Notion already frames your content.

Responsive:

  • Cap width with max-width.
  • Scale padding and font sizes with clamp() or rem.
  • Test at Notion’s narrow column width and full-width pages.

The goal: instant render inside the Notion block.


Troubleshooting common embed issues

Blank iframe? Your host might be sending X-Frame-Options: DENY or a restrictive frame-ancestors. Check your response headers with your browser’s devtools or curl -I.

“Refused to connect” error? Some services disallow being embedded off-site. There’s nothing you can fix client-side if the server forbids it. Host your own page or use the platform’s official embed URL.

Layout looks cramped in Notion? Give your widget a neutral background and generous inner padding. Notion’s outer padding is tight; compensate inside your card.

Font looks different in Notion? Ensure the font loads over HTTPS and isn’t blocked by CSP. Always define a robust fallback stack.


Advanced ideas: states, theming, interactivity

Even with plain HTML/CSS, you can do a lot:

  • Data attributes like data-state="active" for styling states.
  • CSS variables for light/dark themes you toggle with one class.
  • prefers-color-scheme media query for automatic theming.

Want interactivity?

  • Add a tiny script that rotates quotes or fetches a status string.
  • Use progressive enhancement: load the script after initial paint.
  • Keep a no-JS fallback so your widget degrades gracefully.

If you need complex state, authentication, or database sync, jump to Blocky (https://blocky.so). It already solves the hard bits, from timers to charts to study widgets that talk to Notion.


Wrap-up: you’ve got options (and momentum)

Build Your Own Notion Widget: A Beginner’s Guide to Custom HTML & CSS isn’t about theory; it’s a repeatable flow: design a small card, host it, and embed it.

When I need custom styling or a quirky layout, I hand-craft the widget. When I need reliable features like charts, timers, or study tools that sync with Notion, I use Blocky (https://blocky.so) and move on.

Both approaches coexist beautifully. Use the right one for the job. Your Notion pages will look sharper, feel faster, and communicate more at a glance. That’s the win.


Free Basic Clock Example

Here’s a basic example — a simple live digital clock you can host and embed:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>Custom Notion Clock</title>
		<style>
			body {
				margin: 0;
				display: flex;
				justify-content: center;
				align-items: center;
				height: 100vh;
				background: #111827;
				color: #22d3ee;
				font-family: sans-serif;
				font-size: 3rem;
			}
		</style>
	</head>
	<body>
		<div id="clock"></div>
		<script>
			function updateClock() {
				const now = new Date();
				const time = now.toLocaleTimeString([], {
					hour: '2-digit',
					minute: '2-digit',
				});
				document.getElementById('clock').textContent = time;
			}
			setInterval(updateClock, 1000);
			updateClock();
		</script>
	</body>
</html>

Other Articles

  • How To Gamify Notion
  • Notion For Product Management
  • How To Make A Pie Chart
  • Create A Flashcard System
  • How To Add Live Data Widgets

Add a Custom Widget to Your Notion Page

Create your own or customize one of Blocky’s 60+ widgets to make your Notion dashboard truly yours.

Start Customizing

On this page

  • Why build your own Notion widget?
  • How Notion embeds work (and what Notion allows)
  • Build vs. buy: when Blocky is smarter
  • HTML essentials you’ll actually use
  • CSS fundamentals for reliable styling
  • Designing your first widget (layout, type, color)
  • Fonts the right way (fast and legal)
  • Hosting options and going live (pick one, ship today)
  • Embedding your widget in Notion (the exact steps)
  • Securing iframes, CSP, and anti-clickjacking basics
  • Performance and polish (fonts, images, responsive)
  • Troubleshooting common embed issues
  • Advanced ideas: states, theming, interactivity
  • Wrap-up: you’ve got options (and momentum)
  • Free Basic Clock Example
  • Other Articles