Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div 
     x-data="todoList"
     class="todos p-4 lg:py-20 w-full min-h-[100%] flex flex-col items-center bg-gradient-to-br from-slate-700 to-slate-800"
     :class="{ 'justify-center': ((todos.default.length + todos.completed.length + todos.priority.length) < 1) }"
>
    <h1 
        x-show="loaded"
        style="display: none"
        class="mt-6 text-slate-300 text-center border-b-2 border-sky-300 pb-2"
        :class="{ '-mt-20': ((todos.default.length + todos.completed.length + todos.priority.length) < 1) }"
    >
        <span class="text-5xl block uppercase font-bold mb-2 text-white">Todos</span>
        A simple todo list with AlpineJS & TailwindCSS
    </h1>
    
    <!-- Form -->
    <form 
        x-show="loaded"
        style="display: none"
        class="flex flex-wrap w-full max-w-4xl mx-auto mt-4 bg-slate-800 p-2 lg:p-4 rounded shadow-lg shadow-slate-900/25 sticky top-0"
    >
        <input 
               type="text"
               x-ref="todoInput"
               x-model="todoInput"
               class="p-3 rounded flex-auto mr-2 w-52 focus:outline-none bg-slate-400 touch-none placeholder:text-slate-500"
               :class="{ 'text-red-500': unique }"
               @keyup.enter.prevent.stop="todoInput.trim().length > 3 && $refs.todoBtn"
               :disabled="unique"
               placeholder="Enter a new todo"
        >
        
        <button 
                x-ref="todoBtn"
                type="submit"
                @click.prevent.stop="validate"
                class="flex items-center py-1 px-3 bg-slate-600 rounded"
                :class="{ 'bg-sky-500 text-white': todoInput.trim().length > 3 }"
                :disabled="todoInput.trim().length < 3 || unique"
        >
            <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
              <path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd" />
            </svg>
            Add Todo
        </button>
        
        <!-- Error info -->
        <div style="display: none" x-show="unique" class="w-full mt-2 -mb-2 flex flex-wrap items-center">
            <p class="flex-auto text-red-500">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block" viewBox="0 0 20 20" fill="currentColor">
                    <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
                </svg>
                Your Todo needs to be unique
            </p>
            <button 
                    type="button" 
                    class="bg-red-500 hover:bg-emerald-500 text-white text-sm py-1 px-2 rounded"
                    @click.prevent="resetValidation"
            >
                OK
            </button>
        </div>
        
    </form>
    
    <!-- ToDo's Priority Head -->
    <template x-if="todos.priority.length > 0">
        <header class="bg-amber-500 text-amber-900 font-bold w-full max-w-4xl mx-auto mt-6 px-2 py-4 lg:p-4 rounded flex flex-wrap justify-between border border-amber-400">
            <h2 class="uppercase">
                Priority Todos :
                <span x-text="todos.priority.length"></span> 
            </h2>
            
            <button type="button" 
                    class="bg-amber-900 text-amber-300 px-1 rounded"
                    @click="collapseGroup('priorityList')"
                    >
                <svg xmlns="http://www.w3.org/2000/svg" 
                     class="h-5 w-5 transition" 
                     :class="{ 'rotate-180': todos.collapsed.includes('priorityList') }"
                     viewBox="0 0 20 20" 
                     fill="currentColor"
                     >
                  <path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd" />
                </svg>
            </button>
        </header>
    </template>
    <!-- ToDo's Priority List -->
    <template x-if="todos.priority.length > 0">
        <ul 
            class="w-full max-w-4xl mx-auto space-y-1"
            x-show="!todos.collapsed.includes('priorityList')"
            style="display: none"
            x-collapse
            >
            <template x-for="(todo, index) in todos.priority" :key="index">
                <li class="bg-slate-500/75 p-2 lg:px-4 rounded flex flex-wrap items-center text-slate-50 shadow-md shadow-slate-800/50 border border-slate-500">
                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1 text-amber-500" viewBox="0 0 20 20" fill="currentColor">
                        <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                    </svg>
                    <span 
                        class="flex-auto truncate"
                        x-text="todo.name"
                    >
                    </span>
                    
                    <ul class="flex text-lg text-white space-x-1">                        
                        <!-- Completed Icon -->
                        <li>
                            <button
                                type="button"
                                class="p-2 hover:bg-emerald-500 bg-slate-800 rounded"
                                @click="checkTodo('completed', todo.name, 'priority')"
                            >
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
                                    <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
                                </svg>
                            </button>
                        </li>
                        
                        <!-- Star Icon -->
                        <li>
                            <button
                                type="button"
                                class="p-2 bg-amber-500 hover:bg-slate-500 rounded"
                                @click="checkTodo('priority', todo.name, 'priority')"
                            >
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
                                  <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                                </svg>
                            </button>
                        </li>
                        
                        <!-- Delete Icon -->
                        <li>
                            <button
                                type="button"
                                class="p-2 cursor-pointer bg-rose-500 hover:bg-rose-700 rounded"
                                @click="removeTodo(todo.name)"
                            >
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
                                    <path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
                                </svg>
                            </button>
                        </li>
                    </ul>
                </li>
            </template>
        </ul>
    </template>
    
    <!-- ToDo's Default Head -->
    <template x-if="todos.default.length > 0">
        <header class="bg-sky-500 text-sky-900 font-bold w-full max-w-4xl mx-auto mt-6 px-2 py-4 lg:p-4 rounded flex flex-wrap justify-between border border-sky-400">
            <h2 class="uppercase">
                Default Todos :
                <span x-text="todos.default.length"></span> 
            </h2>
            
            <button type="button" 
                    class="bg-sky-900 text-sky-300 px-1 rounded"
                    @click="collapseGroup('defaultList')"
                    >
                <svg xmlns="http://www.w3.org/2000/svg" 
                     class="h-5 w-5 transition" 
                     :class="{ 'rotate-180': todos.collapsed.includes('defaultList') }"
                     viewBox="0 0 20 20" 
                     fill="currentColor"
                     >
                  <path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd" />
                </svg>
            </button>
        </header>
    </template>
    <!-- ToDo's Default List -->
    <template x-if="todos.default.length > 0">
        <ul 
            class="w-full max-w-4xl mx-auto space-y-1"
            x-show="!todos.collapsed.includes('defaultList')"
            style="display: none"
            x-collapse
            >
            <template x-for="(todo, index) in todos.default" :key="index">
                <li class="bg-slate-600 p-2 lg:px-4 rounded flex flex-wrap items-center text-slate-50 shadow-md shadow-slate-800/50 border border-slate-500">
                    <span class="flex-auto truncate" x-text="todo.name"></span>
                    
                    <ul class="flex text-lg text-white space-x-1">     
                        <!-- Completed Icon -->
                        <li>
                            <button
                                type="button"
                                class="p-2 hover:bg-emerald-500 bg-slate-800 rounded"
                                @click="checkTodo('completed', todo.name, 'default')"
                            >
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
                                    <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
                                </svg>
                            </button>
                        </li>
                        
                        <!-- Star Icon -->
                        <li>
                            <button
                                type="button"
                                class="p-2 bg-slate-800 hover:bg-slate-500 rounded"
                                @click="checkTodo('priority', todo.name, 'default')"
                            >
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
                                  <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                                </svg>
                            </button>
                        </li>
                        
                        <!-- Delete Icon -->
                        <li>
                            <button
                                type="button"
                                class="p-2 cursor-pointer bg-rose-500 hover:bg-rose-700 rounded"
                                @click="removeTodo(todo.name)"
                            >
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
                                    <path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
                                </svg>
                            </button>
                        </li>
                    </ul>
                </li>
            </template>
        </ul>
    </template>
    
    <!-- ToDo's Completed Head -->
    <template x-if="todos.completed.length > 0">
        <header class="bg-emerald-500 text-emerald-900 font-bold w-full max-w-4xl mx-auto mt-6 px-2 py-4 lg:p-4 rounded flex flex-wrap justify-between border border-emerald-400">
            <h2 class="uppercase">
                Completed Todos :
                <span x-text="todos.completed.length"></span> 
            </h2>
            
            <button type="button" 
                    class="bg-emerald-900 text-emerald-300 px-1 rounded"
                    @click="collapseGroup('completedList')"
                    >
                <svg xmlns="http://www.w3.org/2000/svg" 
                     class="h-5 w-5 transition" 
                     :class="{ 'rotate-180': !todos.collapsed.includes('completedList') }"
                     viewBox="0 0 20 20" 
                     fill="currentColor"
                     >
                  <path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd" />
                </svg>
            </button>
        </header>
    </template>
    <!-- ToDo's Completed List -->
    <template x-if="todos.completed.length > 0">
        <ul 
            class="w-full max-w-4xl mx-auto space-y-1" 
            x-show="todos.collapsed.includes('completedList')"
            style="display: none"
            x-collapse
            >
            <template x-for="(todo, index) in todos.completed" :key="index">
                <li class="bg-slate-600 p-2 lg:px-4 rounded flex flex-wrap items-center text-slate-50 shadow-md shadow-slate-800/50 text-emerald-500 border border-slate-500">
                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
                        <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
                    </svg>
                    <span 
                        class="flex-auto truncate line-through"
                        x-text="todo.name"
                    >
                    </span>
                    
                    <ul class="flex text-lg text-white space-x-1">                        
                        <!-- Completed Icon -->
                        <li>
                            <button
                                    type="button"
                                    class="p-2 bg-emerald-500 hover:bg-emerald-500 rounded"
                                    @click="checkTodo('completed', todo.name, 'completed')"
                            >
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
                                      <path d="M4 3a2 2 0 100 4h12a2 2 0 100-4H4z" />
                                      <path fill-rule="evenodd" d="M3 8h14v7a2 2 0 01-2 2H5a2 2 0 01-2-2V8zm5 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" clip-rule="evenodd" />
                                </svg>
                            </button>
                        </li>
                        
                        <!-- Delete Icon -->
                        <li>
                            <button
                                type="button"
                                class="p-2 cursor-pointer bg-rose-500 hover:bg-rose-700 rounded"
                                @click="removeTodo(todo.name)"
                            >
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
                                    <path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
                                </svg>
                            </button>
                        </li>
                    </ul>
                </li>
            </template>
        </ul>
    </template>
</div>

<script>
    document.addEventListener('alpine:init', () => {
        Alpine.data('todoList', () => ({
            loaded    : false,
            getStorage: JSON.parse(localStorage.getItem('todos')),
            todoInput : '',
            unique    : false,
            allTodos  : [],
            todos     : {
                default  : [],
                completed: [],
                priority : [],
                collapsed: [],
            },
            
            init() {
                // Check local storage
                for (const key in this.getStorage) {
                    if (this.getStorage.hasOwnProperty(key)) {
                        const obj = this.getStorage[key];

                        for (const item in obj) {
                            obj.hasOwnProperty(item) && this.allTodos.push(obj[item])
                        }
                    }
                }           
                    
                // Filter todos
                this.todos.collapsed = this.allTodos.filter(item => !(item.priority || item.completed || item.default));
                this.filterTodos();
                
                // Show todos
                this.loaded = true;
            },
            
            validate() {
                let validation;
                this.allTodos.forEach(todo => {
                    (todo.name.toLowerCase() === this.todoInput.toLowerCase()) ? validation = true : validation = false;
                });
                
                if (validation) {
                    this.unique = true;
                    return;
                }
                
                this.checkTodo();
            },
            
            resetValidation() {
                this.unique = false;
                this.todoInput = '';
                setTimeout(() => {
                    this.$refs.todoInput.focus();
                }, 300);
            },
            
            checkTodo(value, item, list) {
                switch(value) {
                    case 'completed':
                    case 'priority':
                    case 'default':
                        // Get current todo
                        let currentTodo;
                        this.todos[list].filter(todo => (todo.name === item) && (currentTodo = todo));
                        // Check values
                        if (value === 'completed') currentTodo.completed = !currentTodo.completed; 
                        if (value === 'priority') currentTodo.priority = !currentTodo.priority; 
                        (list === 'default') ? currentTodo.default = false : currentTodo.default = true;
                        // Reorder todo list
                        this.todos[list] = this.todos[list].filter(todo => todo !== currentTodo);
                        break;
                    default:
                        this.allTodos.push({ 
                            name     : this.todoInput, 
                            default  : true,
                            completed: false,
                            priority : false
                        });

                        this.todoInput = '';
                        this.$refs.todoInput.focus();
                }
                
                // Filter all Todos
                this.filterTodos();
            },
            
            filterTodos() {     
                this.todos.completed = this.allTodos.filter(item => (item.completed || (item.completed && item.priority)));
                this.todos.priority  = this.allTodos.filter(item => !item.completed && item.priority);
                this.todos.default   = this.allTodos.filter(item => item.default && !item.completed && !item.priority);  
                
                // Refresh Storage
                this.refreshStorage();
            },
            
            collapseGroup(id) {
                if (this.todos.collapsed.includes(id)) {
                    this.todos.collapsed = this.todos.collapsed.filter(item => item !== id)
                } else {
                    this.todos.collapsed.push(id);
                }
                
                // Refresh Storage
                this.refreshStorage();
            },
            
            refreshStorage() {
                if (this.todos.default.length === 0 && this.todos.completed.length === 0 && this.todos.priority.length === 0 && this.todos.collapsed.length === 0) {
                    localStorage.removeItem('todos');
                    return;
                }
                
                localStorage.setItem('todos', JSON.stringify(this.todos));
            },
            
            removeTodo(item) {
                this.todos.default   = this.todos.default.filter(todo => todo.name !== item);
                this.todos.completed = this.todos.completed.filter(todo => todo.name !== item);
                this.todos.priority  = this.todos.priority.filter(todo => todo.name !== item);
                this.allTodos        = this.allTodos.filter(todo => todo.name !== item);
                
                // Refresh Storage
                this.refreshStorage();
            },
            
        }))
    })
</script>
              
            
!

CSS

              
                @import url("https://fonts.googleapis.com/css?family=Montserrat:400,400i,700");

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html,
body {
    height: 100%;
}

body {
    color: #0f172a;
    background-color: #334155;
    font-family: Montserrat, sans-serif;
}

              
            
!

JS

              
                
              
            
!
999px

Console