Overview
Palette cards store collections of colors with optional names. Teak can extract colors from text, images, or accept manually defined palettes.
Creating Palette Cards
Manual Colors
Explicitly provide colors when creating a palette:
import { api } from "@teak/convex";
import { useMutation } from "convex/react";
const createCard = useMutation(api.card.createCard.createCard);
await createCard({
content: "Sunset palette",
type: "palette",
colors: [
{
hex: "#FF6B35",
name: "Coral",
rgb: { r: 255, g: 107, b: 53 },
hsl: { h: 16, s: 100, l: 60 }
},
{
hex: "#F7931E",
name: "Orange"
},
{
hex: "#FDC830",
name: "Yellow"
}
]
});
If you omit colors, Teak extracts hex codes from content, notes, and tags:
await createCard({
content: "My palette: #FF6B35 #F7931E #FDC830",
type: "palette"
// Colors auto-extracted: ["#FF6B35", "#F7931E", "#FDC830"]
});
See the extraction logic in packages/convex/card/createCard.ts:138-151.
Image cards automatically get palette extraction:
await createCard({
content: "Sunset photo",
type: "image",
fileId: storageId
// Palette extracted automatically during processing
});
Palette extraction runs in parallel with AI metadata generation for better performance.
Color Object Structure
interface Color {
hex: string; // Required: "#FF6B35"
name?: string; // Optional: "Coral"
rgb?: { r: number; g: number; b: number }; // Optional: { r: 255, g: 107, b: 53 }
hsl?: { h: number; s: number; l: number }; // Optional: { h: 16, s: 100, l: 60 }
}
Validation
Colors are validated using the colorValidator schema:
import { v } from "convex/values";
const colorValidator = v.object({
hex: v.string(),
name: v.optional(v.string()),
rgb: v.optional(v.object({
r: v.number(),
g: v.number(),
b: v.number()
})),
hsl: v.optional(v.object({
h: v.number(),
s: v.number(),
l: v.number()
}))
});
Color Facets
Teak builds searchable facets from palette colors:
Hex Facets
Exact hex codes for precise matching:
{
colors: [
{ hex: "#FF6B35" },
{ hex: "#F7931E" }
],
colorHexes: ["#FF6B35", "#F7931E"] // Facet array
}
Hue Buckets
Colors are categorized into 11 hue buckets:
| Hue | Hex Range | Example |
|---|
| red | 345-15° | #FF0000 |
| orange | 15-45° | #FF8800 |
| yellow | 45-75° | #FFFF00 |
| green | 75-165° | #00FF00 |
| teal | 165-180° | #00FFAA |
| cyan | 180-195° | #00FFFF |
| blue | 195-255° | #0000FF |
| purple | 255-300° | #8800FF |
| pink | 300-345° | #FF00FF |
| brown | Low saturation warm | #8B4513 |
| neutral | Grayscale | #888888 |
{
colors: [
{ hex: "#FF6B35" }, // Hue: 16° → orange
{ hex: "#F7931E" }, // Hue: 35° → orange
{ hex: "#FDC830" } // Hue: 47° → yellow
],
colorHues: ["orange", "yellow"] // Unique hue buckets
}
Hue bucketing is implemented in packages/convex/shared/utils/colorUtils.ts.
Teak uses utility functions to extract colors from various sources:
From Text
import { extractPaletteColors } from "@teak/convex/shared/utils/colorUtils";
const text = `
My palette:
#FF6B35 - Coral
#F7931E - Orange
#FDC830 - Yellow
`;
const colors = extractPaletteColors(text, 12);
// Returns: [
// { hex: "#FF6B35", name: "Coral" },
// { hex: "#F7931E", name: "Orange" },
// { hex: "#FDC830", name: "Yellow" }
// ]
From Images
Image palette extraction uses the workflow step:
// From cardProcessing.ts:110-128
const palettePromise = classification.type === "image" && !isSvgImage
? step.runAction(
internal.workflows.steps.palette.extractPaletteFromImage,
{ cardId }
)
: Promise.resolve(null);
SVG images need thumbnail generation first, so palette extraction runs after renderables.
Searching by Color
Exact Hex Match
const orangePalettes = useQuery(api.card.getCards.searchCards, {
hexFilters: ["#FF6B35"],
types: ["palette"],
limit: 50
});
Hue Match
const warmPalettes = useQuery(api.card.getCards.searchCards, {
hueFilters: ["orange", "yellow", "red"],
types: ["palette", "image"],
limit: 50
});
Combined Filters
const vibrantOrangePalettes = useQuery(api.card.getCards.searchCards, {
types: ["palette"],
hueFilters: ["orange"],
styleFilters: ["vibrant"],
limit: 50
});
Combining hexFilters and hueFilters creates an OR condition: cards matching either filter are returned.
Palette Limits
Teak extracts up to 12 colors per palette:
// From createCard.ts:148
const parsedColors = extractPaletteColors(paletteText, 12);
If you provide more than 12 colors manually, they’ll all be stored, but extracted palettes are capped at 12.
Color Utilities
Teak provides utility functions for color manipulation:
Building Color Facets
import { buildColorFacets } from "@teak/convex/shared/utils/colorUtils";
const { colorHexes, colorHues } = buildColorFacets([
{ hex: "#FF6B35" },
{ hex: "#F7931E" },
{ hex: "#FDC830" }
]);
// colorHexes: ["#FF6B35", "#F7931E", "#FDC830"]
// colorHues: ["orange", "yellow"]
Validating Hex Codes
const isValidHex = (hex: string): boolean => {
return /^#[0-9A-Fa-f]{6}$/.test(hex);
};
isValidHex("#FF6B35"); // true
isValidHex("#FFF"); // false (3-digit not supported)
isValidHex("FF6B35"); // false (missing #)
Teak only supports 6-digit hex codes (#RRGGBB). 3-digit codes must be expanded.
Visual Style Detection
Palettes can have visual style tags for aesthetic filtering:
await createCard({
content: "Pastel sunset palette",
type: "palette",
colors: [
{ hex: "#FFB3BA" },
{ hex: "#FFDFBA" },
{ hex: "#FFFFBA" }
],
tags: ["pastel", "soft", "sunset"]
// "pastel" tag creates visualStyles: ["pastel"] facet
});
Add style keywords to tags to enable visual filtering. See Organizing Cards for available styles.
Examples
Tab Title
Tab Title
Tab Title
Tab Title
await createCard({
content: "Company brand colors",
type: "palette",
tags: ["brand", "work"],
colors: [
{ hex: "#1E3A8A", name: "Primary Blue" },
{ hex: "#3B82F6", name: "Secondary Blue" },
{ hex: "#BFDBFE", name: "Light Blue" },
{ hex: "#1F2937", name: "Dark Gray" },
{ hex: "#F3F4F6", name: "Light Gray" }
]
});
// Upload an image
const fileId = await storage.upload(imageFile);
// Create image card (palette extracted automatically)
await createCard({
content: "Sunset photo",
type: "image",
fileId: fileId,
tags: ["nature", "inspiration"]
});
// After processing, card will have:
// colors: [{ hex: "#FF6B35" }, { hex: "#F7931E" }, ...]
// colorHues: ["orange", "yellow", "red"]
await createCard({
content: `
Material Design palette:
#F44336 Red
#E91E63 Pink
#9C27B0 Purple
#673AB7 Deep Purple
`,
type: "palette",
notes: "Material Design color palette for UI design"
});
// Colors extracted from content text
// Find all warm-toned palettes
const warmPalettes = useQuery(api.card.getCards.searchCards, {
types: ["palette"],
hueFilters: ["red", "orange", "yellow"],
limit: 50
});
// Find vibrant color palettes
const vibrantPalettes = useQuery(api.card.getCards.searchCards, {
types: ["palette"],
styleFilters: ["vibrant"],
limit: 50
});
// Find palettes containing specific hex
const specificColor = useQuery(api.card.getCards.searchCards, {
hexFilters: ["#FF6B35"],
limit: 50
});
Best Practices
Provide Color Names
Named colors are easier to search and organize:// Good
{ hex: "#FF6B35", name: "Coral" }
// Works, but less discoverable
{ hex: "#FF6B35" }
Use Tags for Context
{
content: "Sunset palette",
type: "palette",
tags: ["warm", "vibrant", "nature"],
colors: [/* ... */]
}
Combine with Visual Filters
Add style keywords to make palettes discoverable:{
tags: ["pastel", "minimal", "ui-design"],
// Enables filtering by styleFilters: ["pastel", "minimal"]
}
Extract from Screenshots
For design inspiration, save screenshots as image cards:await createCard({
content: "Beautiful UI design",
type: "image",
fileId: screenshotId,
tags: ["ui", "inspiration"]
// Palette extracted automatically
});
Source Reference
- Color extraction:
packages/convex/card/createCard.ts:138-151
- Color facets:
packages/convex/shared/utils/colorUtils.ts
- Color constants:
packages/convex/shared/constants.ts:150-227
- Palette workflow:
packages/convex/workflows/cardProcessing.ts:110-128