Blotch Library
Blotch Library - our purpose-built toolkit designed to streamline Widget development.
It has many useful React components and hooks to help you create great Widgets quickly and easily. With a design system built in, specifically for E-ink displays, you can be sure your Widget will look great on any Blotch Smart Frame.
Why Use the Blotch Library?
The Blotch Library is designed to help you build beautiful, functional Widgets faster and with less code. Here are the key benefits:
E-ink Optimized
All components are specifically designed and optimized for E-ink displays, ensuring crisp visuals and excellent readability on your Blotch Smart Frame.
Developer Experience
Built with TypeScript and React, the library provides full type safety, autocomplete support, and a familiar development experience for modern web developers.
Ready-to-Use Components
Pre-built components for common Widget needs - charts, gauges, QR codes, and more - so you can focus on your Widget's unique features rather than reinventing the wheel.
Third-Party Integrations
Built-in OAuth support and integration helpers make it easy to connect your Widget to external services and APIs.
State Management
Simple, powerful hooks for managing state, storage, and data persistence across Frame updates.
Getting Started
For comprehensive, interactive documentation with live examples and code snippets, visit the Blotch Library Storybook.
- Live component previews - See components in action
- Interactive controls - Modify props and see real-time changes
- Code examples - Copy-paste ready code snippets
- Visual documentation - Playground and usage guidelines
- TypeScript definitions - Full type information for all components and hooks
Importing Components and Hooks
To use any component or hook from the Blotch Library in the Widget Designer, import them using the ~lib/ prefix:
import { ComponentName } from "~lib/package-name";
import { useHookName } from "~lib/hook-name";
The ~lib/ prefix is a special import path that works seamlessly in the Widget Designer environment.
Example Widget
Here's a simple example of a Widget using the Blotch Library:
import { useFetch } from "~lib/use-fetch";
import { BarIndicator } from "~lib/charts/bar-indicator";
type Stats = {
readonly current: number;
readonly total: number;
};
const Widget = () => {
const { data } = useFetch<Stats>("https://api.example.com/stats");
return (
<div>
<h1>Stats Dashboard</h1>
<BarIndicator numerator={data.current} denominator={data.total} />
</div>
);
};
Available Components
Charts
Line charts, bar charts, gauges and more, all drawn to stay readable on E-ink displays. See Charts.
Scannable Components
QRCode
Generate QR codes for URLs, text, or any scannable data.
Import:
import { QRCode } from "~lib/qr-code";
Props:
| Prop | Type | Required | Description |
|---|---|---|---|
text | string | Yes | Content to encode in the QR code |
size | number | No | Size in pixels (default: 256) |
caption | string | No | Text shown with the code, such as "Scan to read" |
captionPlacement | "below" | "beside" | "before" | No | Where the caption sits: under, to the right or to the left (default: "below") |
captionSize | "xs" | "sm" | No | Size of the caption text (default: "sm") |
Example:
<QRCode text="https://blotch.app" size={200} caption="Scan to read more" />
Often useful if you want to link to a website or app from your Widget. For example if you want the user to read more information, or to get more context on their phone.
Available Hooks
Date and Time
Date and Time covers reading the time. Frames are prepared shortly before they go on display, and new Date() gives you the moment your Frame goes on display, so your Widget shows the right time with nothing else to do.
Language
Language covers the language a Frame's owner picks for it. Your Widget's dates and numbers are written in it for you, and usePreferredLanguage tells your Widget which language it is, so it can show its own words in it too.
Data Fetching
Fetching data has its own pages:
- Data Fetching covers
useFetch: requests, caching, errors, retries and response formats. - Geocoding covers
useGeocode, which turns an address into coordinates. - AI covers
useAI, which sends a prompt to an AI model. - Calendar Feeds covers
useCalendarFeed, which reads a calendar someone has shared as a link.
State & Storage
useStorage
Persist data across Frame updates.
Import:
import { useStorage } from "~lib/use-storage";
Usage:
const [data, setData] = useStorage<T>();
Example:
const Widget = () => {
const [data, setData] = useStorage<{ count: number }>();
const increment = () => {
setData({ count: (data?.count || 0) + 1 });
};
return (
<div>
<p>Count: {data?.count || 0}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
Data stored with useStorage persists between Widget updates, making it perfect for maintaining state like OAuth tokens, user preferences, or cached data.
UI State
useDarkMode
Detect the user's dark mode preference.
Import:
import { useDarkMode } from "~lib/use-dark-mode";
Usage:
const isDarkMode = useDarkMode();
Example:
const Widget = () => {
const isDarkMode = useDarkMode();
return (
<div
style={{
background: isDarkMode ? "#000" : "#fff",
color: isDarkMode ? "#fff" : "#000",
}}
>
<h1>{isDarkMode ? "🌙 Dark Mode" : "☀️ Light Mode"}</h1>
</div>
);
};
Third-Party Integrations
RequireIntegration
A component that conditionally renders content based on OAuth integration status.
Import:
import { RequireIntegration } from "~lib/integrations";
Props:
| Prop | Type | Required | Description |
|---|---|---|---|
host | string | Yes* | Integration host (must contain a dot or port, e.g. api.spotify.com) |
name | string | Yes* | Friendly integration name (e.g. Spotify) |
children | ReactNode | Yes | Content to render when authenticated |
* Provide either host or name, not both.
Example:
<RequireIntegration host="auth.my-domain.com">
<div>You are authenticated!</div>
</RequireIntegration>
useIntegration
Access OAuth integration data within a RequireIntegration context.
Import:
import { useIntegration } from "~lib/integrations";
Usage:
const integration = useIntegration();
Return Value:
| Property | Type | Description |
|---|---|---|
host | string | The hostname of the integration |
name | string | The name of the integration |
accessToken | string | undefined | OAuth access token |
refreshToken | string | undefined | OAuth refresh token |
expiresIn | number | undefined | Token expiration time (seconds) |
storedAt | number | Timestamp when token was stored |
widgetUid | string | Unique identifier for the widget |
redirectUri | string | Redirect URI for authentication |
Example:
<RequireIntegration host="api.spotify.com">
{() => {
const integration = useIntegration();
const { data } = useFetch("https://api.spotify.com/v1/me", {
headers: {
Authorization: `Bearer ${integration.accessToken}`,
},
});
return <div>Welcome, {data.display_name}!</div>;
}}
</RequireIntegration>
useIntegration must be used within a component wrapped by RequireIntegration, otherwise it will return null.