VibeWeek
Home/Grow/Tags & Labels System

Tags & Labels System

⬅️ Day 6: Grow Overview

If you're building B2B SaaS in 2026 with content / records / projects / tickets, users expect tags or labels — flexible categorization without forcing rigid hierarchies. Notion, Linear, GitHub, Trello, Gmail, Asana all have them. The naive approach: comma-separated string field. The structured approach: many-to-many tags table, color-coded UI, tag autocomplete + create-on-the-fly, filtering / search by tag, tag analytics, deletion + cascade rules. Tags look simple from outside; the data model and UI patterns require deliberate design. This guide covers the implementation craft.

1. Decide tag model — flat, hierarchical, or scoped

Decide tag model.

Flat tags (most common):
- Simple list: "urgent", "bug", "feature", "design"
- No structure
- Used by: Linear, GitHub Issues, Trello
- Pro: simplicity
- Con: no relationships

Hierarchical tags (rare):
- Nested: "design > color > primary"
- Pro: organization
- Con: complex UX; rarely used well
- Used by: some content tools (Drupal taxonomies)

Scoped tags (recommended for many B2B SaaS):
- Per-resource-type: "Project tags" vs "Customer tags" vs "Document tags"
- Each scope has its own list
- Used by: Notion (per-database tags), Salesforce (per-object)
- Pro: less clutter
- Con: tag duplication across scopes

Workspace-wide tags vs per-list:
- Workspace-wide: shared across all resources in workspace
- Per-list: each list has its own (Trello-style)
- Hybrid: workspace shared, per-list extended

Categories vs tags:
- Categories: structured, exclusive (one project belongs to one category)
- Tags: free-form, multiple
- Use both? Or just tags?

For [USE CASE], output:
1. Recommended model
2. Scope decision (workspace / per-list / per-resource)
3. Tag character limits + format
4. Migration path if changing models
5. Multi-tenant considerations

The 2026 default for B2B SaaS: flat workspace-scoped tags with optional per-resource-type scoping. Notion / Linear pattern.

2. Schema design

Design tags schema.

Core tables:

tags table:
- id (UUID)
- workspace_id (multi-tenant scope)
- name (string, unique within workspace)
- slug (URL-safe; auto-generated from name)
- color (hex / token reference)
- description (optional)
- created_by_user_id
- created_at, updated_at
- deleted_at (soft delete)

resource_tags (many-to-many):
- tag_id (fk)
- resource_type (enum: 'project', 'task', 'customer', etc.)
- resource_id (uuid)
- tagged_by_user_id
- tagged_at

Indexes:
- (workspace_id, slug) unique
- (resource_type, resource_id) for fetching tags on a resource
- (tag_id) for fetching resources with tag

Optional fields:
- emoji (lightweight icon)
- is_system (created automatically; can't delete)
- group / category (if grouping)
- order (manual sort within group)

Polymorphic relations:
- resource_id + resource_type (Rails / Django pattern)
- Or: separate join tables per resource type (project_tags, customer_tags)
- Both work; polymorphic simpler; per-type more queryable

Tag scopes (if scoped):
- Add scope_type / scope_id to tags table
- e.g., scope_type='project'; tags only apply to projects

Migration considerations:
- From string field: parse + dedupe + create
- From CSV column: split + create tags

Output:
1. Schema (DDL)
2. Polymorphic vs per-type decision
3. Indexes
4. Soft-delete policy
5. Migration from existing data

The polymorphic-vs-per-type debate: polymorphic (single resource_tags table) is simpler. Per-type (project_tags, customer_tags) gives type-safety + indexes. For most B2B SaaS, polymorphic is fine.

3. Tag input UI — combobox with create-on-the-fly

Implement tag input UI.

Pattern: combobox

User experience:
- Click tag area → input opens
- Type to filter existing tags
- See suggestions
- Click to add OR
- Press Enter to create new (if doesn't exist)
- Press Escape to close
- Click X on tag to remove

Components:
- shadcn/ui Combobox + Badge
- Or: cmdk + custom badges
- Or: react-select for full feature
- Headless: Downshift, react-aria

Typeahead behavior:
- Search prefix-match (or fuzzy)
- Sort by recently-used + alphabetical
- "+Create '[tag]'" option at bottom for new

Multi-select:
- Allow multiple tags per resource
- Display as pills / badges
- Each removable via X

Create-on-the-fly:
- If user types tag that doesn't exist + presses Enter
- Show "Create tag 'foo'"
- On click: API call to create + add to resource

Validation:
- Min 1 char
- Max 30-50 chars (longer becomes ugly UI)
- No special characters except spaces, hyphens, underscores
- Or: emoji allowed (modern apps)

Color assignment:
- New tags: random color from palette
- User can edit color later in tag management

Mobile:
- Tap to open
- Native keyboard
- Pills with X on touch

Output:
1. Combobox component
2. Search behavior (prefix vs fuzzy)
3. Create-on-the-fly UX
4. Validation rules
5. Mobile pattern

The "create-on-the-fly" pattern is critical: forcing users to manage tags separately before applying them is friction. Type → see suggestions → create new if needed → done.

4. Color coding

Tags without color are harder to scan. Plan colors.

Implement tag colors.

Palette:

Core palette (8-12 colors typical):
- Red, Orange, Yellow, Green, Teal, Blue, Indigo, Purple, Pink, Gray, Brown, Black

Color tokens (Tailwind):
- bg-red-100 + text-red-800 (light theme)
- bg-red-900 + text-red-200 (dark theme)
- Both modes from one config

Color generation:

Auto (recommended for most):
- Hash tag name → color from palette
- Consistent: same tag name = same color across tag instances
- No user choice needed
- Example: hash('bug') → 'red'

User-chosen:
- Pick from palette
- More control
- Used by: Notion, Linear

Hybrid:
- Default to hashed
- User can override

Display:
- Light background + dark text (light mode)
- Dark background + light text (dark mode)
- Border (subtle) for definition
- Rounded full or rounded-md (style choice)

Accessibility:
- 4.5:1 contrast ratio (text on tag background)
- Colorblind-safe palette (avoid red/green-only differentiation)
- Tag name still readable; never color-only meaning

Anti-patterns:
- Too many colors (10+ feels chaotic)
- Inconsistent colors across views
- Color-only signals (colorblind issue)

Output:
1. Color palette
2. Auto vs user-chosen
3. Light / dark mode tokens
4. Accessibility check
5. Border / rounding style

The "auto-color from name hash" pattern: consistent + no user decision needed. If consistency matters less, allow user override.

5. Tag management page

Beyond tagging on resources, users need to manage tags overall.

Build tag management page.

Location:
- Settings → Tags (workspace)
- Or: dedicated tags page in nav (heavy tag use)

Features:

List all tags:
- Name, color, count of tagged resources, created date
- Sortable / searchable

Create tag:
- "New tag" button
- Modal: name + color + description
- Save → tag available for tagging

Edit tag:
- Inline edit name + color
- Cascade: all tagged resources reflect change

Delete tag:
- Confirm modal
- Options: "Delete + remove from N resources" OR "Just delete tag (untag resources)"
- Or: archive (soft delete)

Merge tags:
- Common ask (duplicate "bug" / "bugs")
- Select 2+ tags → merge into one
- All resources with merged tags now have target tag

Permissions:
- Anyone can apply tags? (open)
- Only admins can create / delete? (controlled)
- Workspace setting

Search:
- Find tag by name
- Filter by usage (most/least used)

Bulk operations:
- Multi-select tags
- Bulk delete / merge / change color

Anti-patterns:
- No tag management page (users can't fix tag chaos)
- Delete without "what happens to resources" prompt
- No merge feature (duplicates accumulate)

Output:
1. Page layout
2. CRUD operations
3. Cascade rules
4. Permission model
5. Bulk operations

The merge-tags feature: most-requested by power users. Tag chaos accumulates; merge is the cleanup tool.

6. Filtering by tags

Users filter their content by tag. Plan it.

Implement tag filtering.

Patterns:

Single tag filter:
- Click tag pill → filter to resources with that tag
- URL: ?tag=bug

Multi-tag filter:
- Multi-select tag dropdown
- AND logic: resources have ALL selected tags
- OR logic: resources have ANY selected tag (some products)
- URL: ?tags=bug,urgent

Combined with other filters:
- Tag + status + assignee + date
- Saved views / saved filters

UI patterns:

Tag chips above list:
- Show active tag filters as removable chips
- Click X to remove

Tag dropdown in filter bar:
- Click "Tags" → dropdown with checkboxes
- Apply

Inline tag filter (Linear-style):
- Click tag on resource → filter to that tag
- Quick way to discover related items

Search syntax:
- "tag:bug" or "label:urgent" in search input
- Power-user feature

Empty states:
- Filtered to nothing: "No items with tag 'bug'"
- "Clear filter" link

Performance:
- Index resource_tags by tag_id + resource_type
- Pagination still works
- Server-side filter (don't client-filter large lists)

Output:
1. Filter UI pattern
2. AND vs OR logic
3. URL state
4. Empty states
5. Performance considerations

The Linear pattern: click any tag anywhere → filters list to that tag. Fast tag-based exploration.

7. Tag analytics

Understanding which tags are used informs product decisions.

Track tag analytics.

Per-tag metrics:
- Usage count (how many resources tagged)
- Recency (last used)
- Trend (growing / stagnant)

Workspace-level:
- Total unique tags
- Tag tag-cloud visualization
- Most-used tags
- Unused tags (cleanup opportunity)

User-level:
- User's most-used tags
- Suggest in autocomplete

Reports:
- Cross-tag analysis (tags X + Y together)
- Tag → resource type distribution

Optimization:
- Suggest related tags (collaborative filtering)
- Auto-tag suggestions based on content (ML)
- Detect duplicates ("bug" vs "bugs")

UI:
- Tag management page shows usage count
- Tag analytics dashboard (admin)

Anti-patterns:
- No analytics; tags accumulate without curation
- Auto-tag suggestions that overrule user choice

Output:
1. Metrics tracking
2. Tag-cloud visualization
3. Duplicate detection
4. Related-tag suggestion
5. Admin dashboard

The "unused tags accumulate" reality: 80% of tags get used 1-2 times. Periodic cleanup (delete stale; merge dupes) keeps tag system manageable.

8. AI-suggested tags

LLMs can auto-tag content based on text.

Implement AI tag suggestions.

Pattern:

On create / update of resource:
- Send content to LLM (Claude / GPT-4o)
- Prompt: "Suggest tags from this list: [tags]. Or suggest new ones. Content: [...]"
- Return: array of tag names + confidence

Display:
- Suggested tags appear as ghost-pills (not yet applied)
- User clicks to confirm / dismiss
- Or: auto-apply if confidence > 0.9

Best for:
- Email categorization
- Support ticket triage
- Document content classification
- Content management

Cost considerations:
- Per-resource LLM call
- Cache by content hash
- Batch for bulk operations

Quality:
- LLM tags often overlap with existing
- May suggest creating new ones (allow toggle)
- Refine prompt for your domain

User control:
- Always allow override
- Confidence threshold
- Disable per-user / per-workspace

Privacy:
- Don't send sensitive content to public LLM without checks
- On-prem / private LLMs for high-security

Output:
1. AI suggestion flow
2. Display pattern (ghost vs auto)
3. Cost optimization
4. Privacy considerations
5. User control settings

The "auto-tag with high confidence" pattern: works well for repetitive content (email triage). For knowledge work (notes), suggest only; let user choose.

9. Cross-resource tag operations

Tags connect resources. Make connections useful.

Cross-resource tag operations.

Pattern: click tag → see all resources with that tag

UI:
- Tag pill is clickable
- Clicks navigate to filtered view
- Or: opens panel with related resources

Tag pages (Notion-style):
- Each tag has its own page
- Lists resources with that tag
- Description / notes on the tag itself
- Shareable URL

Tag-based dashboards:
- "Show me all tasks tagged 'urgent'"
- Cross-project / cross-team aggregation

Bulk operations on tagged set:
- Select all "bug"-tagged tasks → bulk action
- Apply / remove tag on bulk

Export:
- Export resources matching tag(s)
- CSV / JSON

Search integration:
- "tag:bug urgent" → search content + tags
- Tag becomes search dimension

Output:
1. Tag-page implementation
2. Cross-resource filtering
3. Bulk operations
4. Search integration
5. Export

The Notion tag-page pattern: each tag is its own discoverable entity. Click tag → see everything with it. Powerful for organization.

10. Permissions + governance

In multi-user products, tag governance matters.

Tag permissions and governance.

Who can:

Apply tags:
- All members (default; flexible)
- Or: only specific roles
- Configurable per workspace

Create new tags:
- All members (most products)
- Or: only admins (controlled)
- Avoids tag explosion

Edit / rename tags:
- Tag creator + admins
- Affects everyone (cascade)

Delete tags:
- Tag creator + admins
- Confirm cascade (resources untagged)

Color / metadata:
- Same as edit permissions

Audit log:
- Tag created / modified / deleted
- Who, when, what changed

Tag locks:
- "System tags" cannot be deleted (e.g., "high-priority" used in automations)
- "Approved tags" curated by admins

Domain governance:
- Tag taxonomy guides (admins set guidelines)
- Naming conventions (e.g., kebab-case)
- Periodic cleanup events

Anti-patterns:
- Anyone can delete any tag (chaos)
- No audit log (untraceable changes)
- Strict admin-only creation (slow; users want flexibility)

Output:
1. Permission matrix
2. Audit logging
3. System / locked tags
4. Governance documentation
5. Cleanup cadence

The "open create + admin curate" balance: members can create tags freely (low friction), admins periodically review + merge (governance). Otherwise tags either explode or stagnate.

What Done Looks Like

A v1 tags / labels system for B2B SaaS in 2026:

  • Schema with workspace-scoped tags + many-to-many resource_tags
  • Tag combobox UI with create-on-the-fly
  • Color-coded tags (auto-hashed or user-chosen)
  • Tag management page (CRUD + merge)
  • Tag-based filtering (single + multi)
  • Click-to-filter from tag pill
  • URL state for filters
  • Multi-tenant isolation
  • Cascade rules on tag delete
  • Permissions (apply / create / delete)
  • Mobile-friendly UX

Add later when product is mature:

  • AI tag suggestions
  • Tag pages (Notion-style)
  • Tag analytics dashboard
  • Bulk tag operations
  • Tag templates
  • Tag governance + audit

The mistake to avoid: comma-separated string column for tags. Querying / filtering / merging / counting becomes pain.

The second mistake: no merge feature. Duplicates accumulate; tag chaos.

The third mistake: tag explosion without governance. 1000+ tags = effectively no tags. Periodic cleanup needed.

See Also