| name | task-tagging |
| description | Provides categorical organization for tasks using a predefined set of seven standard tags (Work, Personal, Shopping, Health, Finance, Learning, Urgent). Enforces tag validation rules including duplicate prevention and 5-tag maximum per task. |
Task Tagging Skill
Overview
The task tagging skill enables contextual categorization of tasks using a standardized set of tags. This skill provides consistent visual styling, validation rules, and user interface behaviors for tag management.
When to Apply
Apply this skill:
- When creating a new task and user adds tags
- When editing an existing task's tags
- During tag input and autocomplete interactions
- When displaying task lists with tag filters
- During bulk import or migration (to validate and apply tags)
- When rendering tag chips in UI components
Standard Tag Categories
This skill defines exactly seven standard tag categories:
- Work - Professional tasks, meetings, projects
- Personal - Personal errands, appointments, life admin
- Shopping - Purchase items, grocery lists, shopping errands
- Health - Medical appointments, exercise, wellness activities
- Finance - Bills, budgeting, financial planning
- Learning - Study, courses, skill development
- Urgent - Time-sensitive tasks requiring immediate attention
Tag Rules and Constraints
Primary and Secondary Tags
- One primary tag: The main category for the task
- Up to 4 secondary tags: Additional contextual tags
- Maximum 5 tags total per task
Example valid combinations:
// Valid: 1 primary + 2 secondary tags
{ tags: ['Work', 'Urgent', 'Finance'] }
// Valid: 1 primary tag only
{ tags: ['Personal'] }
// Valid: 1 primary + 4 secondary tags (max)
{ tags: ['Work', 'Learning', 'Finance', 'Urgent', 'Health'] }
// Invalid: 6 tags (exceeds maximum)
{ tags: ['Work', 'Personal', 'Shopping', 'Health', 'Finance', 'Learning'] }
Duplicate Prevention
No duplicate tags are allowed on the same task:
// Valid: All unique tags
{ tags: ['Work', 'Urgent'] }
// Invalid: 'Work' appears twice
{ tags: ['Work', 'Work', 'Personal'] }
Tag Validation Rules
- Maximum count: 5 tags per task
- No duplicates: Each tag must be unique
- Standard categories only: Tags must be from the predefined set
- Case handling: Tag names are case-sensitive and should match exactly
Tag Color Scheme
Each tag has a specific color for visual consistency:
Work Tag
.tag-work {
background-color: #DBEAFE; /* Light blue background */
color: #1E40AF; /* Dark blue text */
border: 1px solid #93C5FD;
}
- Background: #DBEAFE (light blue)
- Text: #1E40AF (dark blue)
- Border: #93C5FD (medium blue)
Personal Tag
.tag-personal {
background-color: #FCE7F3; /* Light pink background */
color: #BE185D; /* Dark pink text */
border: 1px solid #F9A8D4;
}
- Background: #FCE7F3 (light pink)
- Text: #BE185D (dark pink)
- Border: #F9A8D4 (medium pink)
Shopping Tag
.tag-shopping {
background-color: #FEF3C7; /* Light yellow background */
color: #92400E; /* Dark amber text */
border: 1px solid #FCD34D;
}
- Background: #FEF3C7 (light yellow)
- Text: #92400E (dark amber)
- Border: #FCD34D (medium yellow)
Health Tag
.tag-health {
background-color: #D1FAE5; /* Light green background */
color: #065F46; /* Dark green text */
border: 1px solid #6EE7B7;
}
- Background: #D1FAE5 (light green)
- Text: #065F46 (dark green)
- Border: #6EE7B7 (medium green)
Finance Tag
.tag-finance {
background-color: #E0E7FF; /* Light indigo background */
color: #3730A3; /* Dark indigo text */
border: 1px solid #A5B4FC;
}
- Background: #E0E7FF (light indigo)
- Text: #3730A3 (dark indigo)
- Border: #A5B4FC (medium indigo)
Learning Tag
.tag-learning {
background-color: #FECACA; /* Light red background */
color: #991B1B; /* Dark red text */
border: 1px solid #FCA5A5;
}
- Background: #FECACA (light red)
- Text: #991B1B (dark red)
- Border: #FCA5A5 (medium red)
Urgent Tag
.tag-urgent {
background-color: #FEE2E2; /* Light rose background */
color: #7F1D1D; /* Dark rose text */
border: 1px solid #FECACA;
font-weight: 600; /* Bold for emphasis */
}
- Background: #FEE2E2 (light rose)
- Text: #7F1D1D (dark rose)
- Border: #FECACA (medium rose)
- Font weight: 600 (bold)
UI Chip/Badge Styling
All tag chips follow a consistent design pattern:
.tag-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 12px;
border-radius: 16px; /* Rounded corners */
font-size: 12px;
font-weight: 500;
transition: all 0.2s ease;
cursor: pointer;
}
/* Hover state */
.tag-chip:hover {
opacity: 0.8;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* Active state */
.tag-chip:active {
transform: translateY(0);
box-shadow: none;
}
/* Remove button (X) - hidden by default */
.tag-chip .remove-btn {
opacity: 0;
margin-left: 4px;
font-size: 14px;
cursor: pointer;
transition: opacity 0.2s ease;
}
/* Show remove button on hover */
.tag-chip:hover .remove-btn {
opacity: 1;
}
.tag-chip .remove-btn:hover {
color: #DC2626; /* Red on hover */
}
Visual characteristics:
- Border radius: 16px (fully rounded)
- Padding: 4px 12px
- Font size: 12px
- Font weight: 500
- Gap between text and X button: 4px
- Subtle background colors with contrasting text
- X button appears on hover
- Hover lift effect (translateY -1px)
- Smooth transitions (0.2s)
Tag Autocomplete Behavior
Input Field Interaction
When user types in tag input field:
- Debounce: Wait 150ms after last keystroke
- Filter: Show matching tags from standard categories
- Case-insensitive matching: "wor" matches "Work"
- Prefix matching: Match from start of tag name
- Limit results: Show maximum 5 suggestions
Autocomplete Dropdown
function TagAutocomplete({ inputValue, onSelect }) {
const [suggestions, setSuggestions] = useState([]);
const standardTags = [
'Work', 'Personal', 'Shopping',
'Health', 'Finance', 'Learning', 'Urgent'
];
useEffect(() => {
const timer = setTimeout(() => {
if (inputValue.length > 0) {
const filtered = standardTags.filter(tag =>
tag.toLowerCase().startsWith(inputValue.toLowerCase())
);
setSuggestions(filtered.slice(0, 5));
} else {
setSuggestions([]);
}
}, 150);
return () => clearTimeout(timer);
}, [inputValue]);
return (
<div className="autocomplete-dropdown">
{suggestions.map(tag => (
<div
key={tag}
className="autocomplete-item"
onClick={() => onSelect(tag)}
>
{tag}
</div>
))}
</div>
);
}
Autocomplete Styling
.autocomplete-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #E5E7EB;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-top: 4px;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
}
.autocomplete-item {
padding: 8px 12px;
cursor: pointer;
transition: background-color 0.15s ease;
}
.autocomplete-item:hover {
background-color: #F3F4F6;
}
.autocomplete-item:active {
background-color: #E5E7EB;
}
Tag Removal Interaction
Remove Button Behavior
- Hidden by default: X button has opacity: 0
- Show on hover: Opacity transitions to 1 on chip hover
- Click to remove: Clicking X removes the tag
- Confirmation: Optional confirmation for primary tag removal
- Immediate update: UI updates instantly on removal
Remove Button Implementation
function TagChip({ tag, onRemove, isPrimary }) {
const [showConfirm, setShowConfirm] = useState(false);
const handleRemove = () => {
if (isPrimary && !showConfirm) {
setShowConfirm(true);
setTimeout(() => setShowConfirm(false), 3000);
} else {
onRemove(tag);
setShowConfirm(false);
}
};
return (
<div className={`tag-chip tag-${tag.toLowerCase()}`}>
<span>{tag}</span>
<button
className="remove-btn"
onClick={handleRemove}
aria-label={`Remove ${tag} tag`}
>
×
</button>
{showConfirm && (
<span className="confirm-text">Click again to remove</span>
)}
</div>
);
}
Validation Implementation
Tag Validation Function
function validateTags(tags) {
const errors = [];
const standardTags = [
'Work', 'Personal', 'Shopping',
'Health', 'Finance', 'Learning', 'Urgent'
];
// Check maximum count
if (tags.length > 5) {
errors.push('Maximum 5 tags allowed per task');
}
// Check for duplicates
const uniqueTags = new Set(tags);
if (uniqueTags.size !== tags.length) {
errors.push('Duplicate tags are not allowed');
}
// Check for invalid tags
const invalidTags = tags.filter(tag => !standardTags.includes(tag));
if (invalidTags.length > 0) {
errors.push(`Invalid tags: ${invalidTags.join(', ')}`);
}
return {
isValid: errors.length === 0,
errors
};
}
Validation Error Display
function TagInput({ tags, setTags }) {
const [validationErrors, setValidationErrors] = useState([]);
const handleAddTag = (newTag) => {
const updatedTags = [...tags, newTag];
const validation = validateTags(updatedTags);
if (validation.isValid) {
setTags(updatedTags);
setValidationErrors([]);
} else {
setValidationErrors(validation.errors);
// Show error for 3 seconds
setTimeout(() => setValidationErrors([]), 3000);
}
};
return (
<div className="tag-input-container">
{/* Tag input field and chips */}
{validationErrors.length > 0 && (
<div className="validation-errors">
{validationErrors.map((error, idx) => (
<div key={idx} className="error-message">
{error}
</div>
))}
</div>
)}
</div>
);
}
Complete UI Component Example
function TaskTagManager({ task, onUpdateTags }) {
const [tags, setTags] = useState(task.tags || []);
const [inputValue, setInputValue] = useState('');
const [showAutocomplete, setShowAutocomplete] = useState(false);
const standardTags = [
'Work', 'Personal', 'Shopping',
'Health', 'Finance', 'Learning', 'Urgent'
];
const getTagClassName = (tag) => {
return `tag-chip tag-${tag.toLowerCase()}`;
};
const handleAddTag = (tag) => {
const validation = validateTags([...tags, tag]);
if (validation.isValid) {
const updatedTags = [...tags, tag];
setTags(updatedTags);
onUpdateTags(updatedTags);
setInputValue('');
setShowAutocomplete(false);
} else {
alert(validation.errors.join('\n'));
}
};
const handleRemoveTag = (tagToRemove) => {
const updatedTags = tags.filter(tag => tag !== tagToRemove);
setTags(updatedTags);
onUpdateTags(updatedTags);
};
return (
<div className="task-tag-manager">
{/* Display existing tags */}
<div className="tag-list">
{tags.map((tag, index) => (
<div key={tag} className={getTagClassName(tag)}>
<span>{tag}</span>
<button
className="remove-btn"
onClick={() => handleRemoveTag(tag)}
>
×
</button>
</div>
))}
</div>
{/* Tag input */}
{tags.length < 5 && (
<div className="tag-input-wrapper">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onFocus={() => setShowAutocomplete(true)}
placeholder="Add tag..."
className="tag-input"
/>
{showAutocomplete && (
<TagAutocomplete
inputValue={inputValue}
onSelect={handleAddTag}
/>
)}
</div>
)}
</div>
);
}
Testing Examples
Test Case 1: Valid Tag Addition
const tags = ['Work', 'Urgent'];
const validation = validateTags([...tags, 'Finance']);
// Expected: { isValid: true, errors: [] }
Test Case 2: Maximum Tags Exceeded
const tags = ['Work', 'Personal', 'Shopping', 'Health', 'Finance'];
const validation = validateTags([...tags, 'Learning']);
// Expected: { isValid: false, errors: ['Maximum 5 tags allowed per task'] }
Test Case 3: Duplicate Tag
const tags = ['Work', 'Urgent'];
const validation = validateTags([...tags, 'Work']);
// Expected: { isValid: false, errors: ['Duplicate tags are not allowed'] }
Test Case 4: Invalid Tag
const tags = ['Work'];
const validation = validateTags([...tags, 'Custom']);
// Expected: { isValid: false, errors: ['Invalid tags: Custom'] }
Integration Points
This skill integrates with:
- Priority Classification Skill: Tags can influence priority (e.g., "Urgent" tag)
- Task Filter Skill: Enables filtering by specific tags
- Task Search Skill: Tags are searchable fields
- Task Organization Agent: Uses tagging for contextual organization
Performance Considerations
- Tag validation should complete in < 1ms
- Autocomplete debounce prevents excessive filtering (150ms)
- Tag chip rendering optimized with CSS transitions
- Maximum 5 tags prevents UI clutter and performance issues
Accessibility Considerations
- Remove buttons have aria-label for screen readers
- Tag chips are keyboard navigable
- Color contrast ratios meet WCAG AA standards
- Focus states are clearly visible