30817:profile-themes
Profile Themes
- signed by
- npub1q3sle...6cd26p
- published
- 2026-06-28
- event
- 68f3082051...ce2e
This NIP defines custom profile themes as seen in Ditto. It includes shareable theme events (kind 36767) as well as the user's current profile theme (kind 16767). Additionally, kind 0 events are extended with a "shape" field that may be used to mask the user's avatar with an emoji silhouette instead of a standard circle.
Kind 36767: Theme Definition
Summary
Addressable event kind for publishing shareable custom UI themes. A single user may publish multiple themes, each identified by a unique d tag.
A theme consists of colors, optional fonts, and an optional background. Colors are stored in c tags, fonts in f tags, and background in a bg tag.
Event Structure
{
"kind": 36767,
"content": "",
"tags": [
["d", "mk-dark-theme"],
["c", "#1a1a2e", "background"],
["c", "#e0e0e0", "text"],
["c", "#6c3ce0", "primary"],
["f", "Inter", "https://example.com/inter.woff2", "body"],
["f", "Playfair Display", "https://example.com/playfair.woff2", "title"],
["bg", "url https://example.com/bg.jpg", "mode cover", "m image/jpeg", "dim 1920x1080"],
["title", "MK Dark Theme"],
["alt", "Custom theme: MK Dark Theme"]
]
}
Content
The content field is unused and MUST be an empty string ("").
Tags
| Tag | Required | Description |
|---|---|---|
d |
Yes | Unique identifier (slug) for this theme, e.g. "mk-dark-theme" |
c |
Yes (×3) | Hex color with marker. See Color Tags. |
f |
No | Font declaration. See Font Tag. |
bg |
No | Background media. See Background Tag. |
title |
Yes | Human-readable theme name |
alt |
Yes | NIP-31 human-readable fallback |
Multiple Themes Per User
Since kind 36767 is addressable, a user can publish multiple themes by using different d tag values. Publishing a new event with the same d tag replaces the previous version (this is how editing works).
Kind 16767: Active Profile Theme
Summary
Replaceable event that represents the user's currently active profile theme. Only one per user. When other users visit a profile, they query this kind to determine what theme to display.
Event Structure
{
"kind": 16767,
"content": "",
"tags": [
["c", "#1a1a2e", "background"],
["c", "#e0e0e0", "text"],
["c", "#6c3ce0", "primary"],
["f", "Inter", "https://example.com/inter.woff2", "body"],
["f", "Playfair Display", "https://example.com/playfair.woff2", "title"],
["bg", "url https://example.com/bg.jpg", "mode cover", "m image/jpeg"],
["title", "MK Dark Theme"],
["alt", "Active profile theme"]
]
}
Content
The content field is unused and MUST be an empty string ("").
Tags
| Tag | Required | Description |
|---|---|---|
c |
Yes (×3) | Hex color with marker. See Color Tags. |
f |
No | Font declaration. See Font Tag. |
bg |
No | Background media. See Background Tag. |
title |
No | Human-readable name for the theme |
alt |
Yes | NIP-31 human-readable fallback |
Client Behavior
- When visiting a profile, clients query
{ kinds: [16767], authors: [pubkey], limit: 1 }to get the active theme. - Clients read the
ctags to extract colors,ftags for fonts, andbgtag for the background. - Setting a new active theme publishes a new kind 16767 event (replacing the old one).
- To remove the active theme, publish a kind 5 deletion event targeting kind 16767.
Shared Tag Definitions
The following tag definitions apply to both kind 36767 and kind 16767.
Color Tags
Format: ["c", "#rrggbb", "<marker>"]
| Index | Required | Description |
|---|---|---|
| 0 | Yes | Tag name: "c" |
| 1 | Yes | Lowercase 6-digit hex color code including the # sign (e.g. "#ff0000") |
| 2 | Yes | Color role marker: one of "primary", "text", or "background" |
- All three markers (
"primary","text","background") MUST be present. - Only one
ctag per marker is allowed.
Font Tag
Format: ["f", "<family>", "<url>", "<role>"]
| Index | Required | Description |
|---|---|---|
| 0 | Yes | Tag name: "f" |
| 1 | Yes | CSS font-family name (e.g. "Inter") |
| 2 | Yes | Direct URL to a font file (.woff2, .ttf, .otf) |
| 3 | Yes | Font role: "body" or "title" |
Roles:
| Role | Applies to |
|---|---|
"body" |
All text globally (body, headings, UI elements) |
"title" |
The user's profile display name |
Rules:
- The
ftag is optional on the event. - At most one
ftag per role is allowed (i.e. one body font and one title font). - The
"body"font tag MUST be ordered before the"title"font tag. This ensures backward-compatible clients that only read the firstftag will pick up the body font. - If the URL fails to load, the client SHOULD fall back to a default font gracefully.
- Clients that do not recognize a role SHOULD ignore that
ftag. - Legacy events with an
ftag that has no role marker (only 3 elements) SHOULD be treated as"body". - Variable font files (covering multiple weights in a single file) are preferred.
Background Tag
The bg tag uses an imeta-style variadic format where each entry (after the tag name) is a space-delimited key/value pair.
Format: ["bg", "url <url>", "mode <mode>", "m <mime-type>", ...]
| Key | Required | Description |
|---|---|---|
url |
Yes | URL to an image or video file |
mode |
Yes | Display mode: "cover" or "tile" |
m |
Yes | MIME type (e.g. "image/jpeg", "image/png", "video/mp4") |
dim |
No | Dimensions in pixels: "<width>x<height>" (e.g. "1920x1080") |
blurhash |
No | Blurhash placeholder string for progressive loading |
- At most one
bgtag is allowed per event. - Clients MAY choose not to render video backgrounds for performance or bandwidth reasons.
- Unknown keys SHOULD be ignored for forward compatibility.
Kind 0 Extension: Avatar Shape
Summary
An optional shape property on kind 0 (profile metadata) that controls how the user's avatar is masked/clipped when displayed. The value is an emoji character whose silhouette is used as a mask over the avatar image. When absent, the avatar renders as the standard circle.
Metadata Field
The shape field is added to the JSON content of a kind 0 event alongside standard fields like name, picture, etc. Its value is a single emoji character (including multi-codepoint emoji such as flags, ZWJ sequences, and skin-tone variants).
{
"kind": 0,
"content": "{\"name\":\"Luna\",\"shape\":\"🌙\",\"picture\":\"https://example.com/luna.jpg\"}"
}
Client Behavior
- When
shapeis absent, clients SHOULD render the avatar as a circle (the current universal default). - When
shapeis a valid emoji, clients SHOULD use the emoji's silhouette as an alpha mask over the avatar image. The specific rendering technique is platform-dependent (see below). - When
shapeis set to an unrecognized or invalid value, clients MUST fall back to a circle. This ensures forward compatibility. - The
shapefield is purely cosmetic and has no protocol-level significance. - Clients MAY choose not to support this extension, in which case avatars render as circles as usual.
Cited links
Discussion
Connect a key to comment.