Skip to main content

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​

info

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";
tip

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:

PropTypeRequiredDescription
textstringYesContent to encode in the QR code
sizenumberNoSize in pixels (default: 256)
captionstringNoText shown with the code, such as "Scan to read"
captionPlacement"below" | "beside" | "before"NoWhere the caption sits: under, to the right or to the left (default: "below")
captionSize"xs" | "sm"NoSize of the caption text (default: "sm")

Example:

<QRCode text="https://blotch.app" size={200} caption="Scan to read more" />
tip

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>
);
};
info

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:

PropTypeRequiredDescription
hoststringYes*Integration host (must contain a dot or port, e.g. api.spotify.com)
namestringYes*Friendly integration name (e.g. Spotify)
childrenReactNodeYesContent 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:

PropertyTypeDescription
hoststringThe hostname of the integration
namestringThe name of the integration
accessTokenstring | undefinedOAuth access token
refreshTokenstring | undefinedOAuth refresh token
expiresInnumber | undefinedToken expiration time (seconds)
storedAtnumberTimestamp when token was stored
widgetUidstringUnique identifier for the widget
redirectUristringRedirect 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>
warning

useIntegration must be used within a component wrapped by RequireIntegration, otherwise it will return null.