This is the second post of Building a Minimalist Task Manager series
Welcome back to our vanilla JavaScript task manager series! In the last post, we built a solid foundation with basic add/delete functionality. Today, we’re adding the star of the show: the three-task limit that gives our app its personality and purpose.
But we’re not just slapping on a simple if (tasks.length >= 3) check and calling it a day. We’re going to make this limitation feel intentional and smooth, with visual feedback that guides users rather than frustrating them.
Why Three Tasks?
Before we dive into code, let’s talk about why this limitation exists. The idea is simple: when you have too many tasks staring at you, decision paralysis kicks in. By limiting yourself to just three active tasks, you’re forced to:
- Prioritize what actually matters today
- Finish things before starting new ones
- Avoid the endless scroll of doom that most todo apps become
Think of it as Marie Kondo for your productivity - if it doesn’t spark immediate action, it doesn’t get to stay on the list.
What We’re Adding Today
- The 3-task limit: Users can only have 3 active tasks maximum
- Task completion: Mark tasks as done instead of just deleting them
- Smart UI feedback: Visual cues when you hit the limit
- Better task management: Completed tasks get their own section
Let’s dive in and make our task manager actually enforce some digital discipline!
Updating Our HTML Structure
First, let’s modify our HTML to accommodate completed tasks and better visual feedback. Here are the changes to the task list container:
<!-- Replace the existing task-list-container div with this: -->
<div class="task-list-container">
<div class="active-tasks-section">
<h3>Active Tasks <span id="taskCounter">(0/3)</span></h3>
<ul id="taskList" class="task-list">
<!-- Active tasks will be dynamically added here -->
</ul>
<p id="limitMessage" class="limit-message" style="display: none;">
Focus mode activated! Complete a task to add more.
</p>
</div>
<div class="completed-tasks-section" id="completedSection" style="display: none;">
<h3>Completed Tasks</h3>
<ul id="completedTaskList" class="task-list completed-list">
<!-- Completed tasks will be dynamically added here -->
</ul>
</div>
</div>
Enhanced CSS Styling
Let’s add some new CSS rules to handle our enhanced UI. Add these to your existing styles.css:
/* Add these new styles to your existing styles.css */
.active-tasks-section h3, .completed-tasks-section h3 {
margin-bottom: 1rem;
color: #2d3748;
font-size: 1.2rem;
font-weight: 500;
}
#taskCounter {
font-weight: 300;
color: #718096;
}
.limit-message {
text-align: center;
color: #e53e3e;
font-style: italic;
margin: 1rem 0;
padding: 0.75rem;
background-color: #fed7d7;
border-radius: 6px;
}
.completed-tasks-section {
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid #e2e8f0;
}
.completed-list .task-item {
opacity: 0.7;
background-color: #f7fafc;
}
.completed-list .task-text {
text-decoration: line-through;
color: #718096;
}
.complete-btn {
background-color: #48bb78;
color: white;
border: none;
padding: 0.25rem 0.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.875rem;
margin-right: 0.5rem;
}
.complete-btn:hover {
background-color: #38a169;
}
/* Update the existing #addTaskBtn to show disabled state */
#addTaskBtn:disabled {
background-color: #a0aec0;
cursor: not-allowed;
}
#addTaskBtn:disabled:hover {
background-color: #a0aec0;
}
The Enhanced JavaScript Logic
Now for the main event - let’s update our JavaScript to implement the focus mode. Here’s the enhanced script.js:
// Get references to our DOM elements
const taskInput = document.getElementById('taskInput');
const addTaskBtn = document.getElementById('addTaskBtn');
const taskList = document.getElementById('taskList');
const completedTaskList = document.getElementById('completedTaskList');
const completedSection = document.getElementById('completedSection');
const taskCounter = document.getElementById('taskCounter');
const limitMessage = document.getElementById('limitMessage');
// Constants
const MAX_ACTIVE_TASKS = 3;
// Arrays to store our tasks
let activeTasks = [];
let completedTasks = [];
// Function to update the task counter and UI state
function updateUI() {
// Update counter
taskCounter.textContent = `(${activeTasks.length}/${MAX_ACTIVE_TASKS})`;
// Show/hide limit message and disable button if at limit
const atLimit = activeTasks.length >= MAX_ACTIVE_TASKS;
limitMessage.style.display = atLimit ? 'block' : 'none';
addTaskBtn.disabled = atLimit;
// Show/hide completed section
completedSection.style.display = completedTasks.length > 0 ? 'block' : 'none';
console.log(`Active: ${activeTasks.length}, Completed: ${completedTasks.length}`);
}
// Function to create a new active task element
function createActiveTaskElement(taskText, taskId) {
const li = document.createElement('li');
li.className = 'task-item';
li.innerHTML = `
<span class="task-text">${taskText}</span>
<div>
<button class="complete-btn" onclick="completeTask(${taskId})">Done</button>
<button class="delete-btn" onclick="deleteTask(${taskId})">Delete</button>
</div>
`;
return li;
}
// Function to create a completed task element
function createCompletedTaskElement(taskText, taskId) {
const li = document.createElement('li');
li.className = 'task-item';
li.innerHTML = `
<span class="task-text">${taskText}</span>
<button class="delete-btn" onclick="deleteCompletedTask(${taskId})">Remove</button>
`;
return li;
}
// Function to add a new task
function addTask() {
const taskText = taskInput.value.trim();
// Check if input is empty
if (taskText === '') {
alert('Please enter a task!');
return;
}
// Check if we're at the limit
if (activeTasks.length >= MAX_ACTIVE_TASKS) {
alert(`Focus mode! You can only have ${MAX_ACTIVE_TASKS} active tasks. Complete one first.`);
return;
}
// Create task object
const task = {
id: Date.now(),
text: taskText,
createdAt: new Date()
};
// Add to active tasks array
activeTasks.push(task);
// Create and add the task element to the DOM
const taskElement = createActiveTaskElement(task.text, task.id);
taskList.appendChild(taskElement);
// Clear the input field
taskInput.value = '';
// Update UI state
updateUI();
}
// Function to complete a task (move from active to completed)
function completeTask(taskId) {
// Find the task in active tasks
const taskIndex = activeTasks.findIndex(task => task.id === taskId);
if (taskIndex === -1) return;
const task = activeTasks[taskIndex];
// Move to completed tasks
task.completedAt = new Date();
completedTasks.push(task);
activeTasks.splice(taskIndex, 1);
// Remove from active DOM and add to completed DOM
removeTaskElementById(taskList, taskId);
const completedElement = createCompletedTaskElement(task.text, task.id);
completedTaskList.appendChild(completedElement);
// Update UI state
updateUI();
}
// Function to delete an active task
function deleteTask(taskId) {
// Remove from active tasks array
activeTasks = activeTasks.filter(task => task.id !== taskId);
// Remove from DOM
removeTaskElementById(taskList, taskId);
// Update UI state
updateUI();
}
// Function to delete a completed task
function deleteCompletedTask(taskId) {
// Remove from completed tasks array
completedTasks = completedTasks.filter(task => task.id !== taskId);
// Remove from DOM
removeTaskElementById(completedTaskList, taskId);
// Update UI state
updateUI();
}
// Helper function to remove task element by ID
function removeTaskElementById(parentElement, taskId) {
const taskElements = parentElement.children;
for (let i = 0; i < taskElements.length; i++) {
const buttons = taskElements[i].querySelectorAll('button');
for (let button of buttons) {
if (button.onclick && button.onclick.toString().includes(taskId)) {
parentElement.removeChild(taskElements[i]);
return;
}
}
}
}
// Event listeners
addTaskBtn.addEventListener('click', addTask);
// Allow adding tasks by pressing Enter (but respect the limit)
taskInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
addTask();
}
});
// Initialize UI on page load
updateUI();
What’s New and Improved
The magic happens in several key areas:
Task Limit Enforcement: The
addTask()function now checks if we’re at the 3-task limit before allowing new tasks.Smart UI Updates: The
updateUI()function handles the counter, button states, and visual feedback all in one place.Task Completion: Instead of just deleting tasks, we can now mark them as done, which moves them to a separate completed section.
Visual Feedback: When you hit the 3-task limit, the add button gets disabled and a friendly message appears.
Better Organization: Active and completed tasks are clearly separated, making it easy to see your progress.
The Psychology Behind the Magic
Here’s why this 3-task limit is actually brilliant:
- Reduces decision paralysis: Instead of staring at a massive list, you focus on just 3 things
- Creates urgency: Limited slots make you prioritize what really matters
- Builds momentum: Completing tasks feels more rewarding when you can immediately add something new
- Prevents overwhelm: Your brain can actually handle 3 priorities at once
Try It Out!
Go ahead and test the enhanced version:
- Add your first task - notice the counter updates
- Add two more tasks - you’ll see you’re at the limit
- Try to add a fourth task - the app politely refuses
- Mark a task as “Done” - watch it move to the completed section
- Notice how the add button becomes available again
Pretty satisfying, right?
What’s Coming Next
In our next post, we’ll add localStorage to persist your tasks between browser sessions (because losing your carefully curated 3-task list would be tragic), and we might even add some task reordering functionality.
The focus mode is working, but we’re just getting started. Next time, we’ll make sure your digital discipline survives browser refreshes!
Keep coding, and remember - less is more!