diff --git a/src/snek/static/chat-input.js b/src/snek/static/chat-input.js
index c1d767d..2d0914e 100644
--- a/src/snek/static/chat-input.js
+++ b/src/snek/static/chat-input.js
@@ -1,69 +1,234 @@
-// Written by retoor@molodetz.nl
 
-// This JavaScript class defines a custom HTML element for a chat input widget, featuring a text area and an upload button. It handles user input and triggers events for input changes and message submission.
+import { app } from '../app.js';
 
-// Includes standard DOM manipulation methods; no external imports used.
+class ChatInputComponent extends HTMLElement {
+    autoCompletions = {
+        'example 1': () => {
 
-// MIT License: This code is open-source and can be reused and distributed under the terms of the MIT License.
+        },
+        'example 2': () => {
 
-class ChatInputElement extends HTMLElement {
-  _chatWindow = null 
-  constructor() {
-    super();
-    this.attachShadow({ mode: 'open' });
-    this.component = document.createElement('div');
-    this.shadowRoot.appendChild(this.component);
-  }
-  set chatWindow(value){
-    this._chatWindow = value 
+        }
+    }
 
-  }
-  get chatWindow(){
-    return this._chatWindow 
-  }
-  get channelUid() {
-    return this.chatWindow.channel.uid
-  }
-  connectedCallback() {
-    const link = document.createElement('link');
-    link.rel = 'stylesheet';
-    link.href = '/base.css';
-    this.component.appendChild(link);
+    constructor() {
+        super();
+        this.lastUpdateEvent = new Date();
+        this.textarea = document.createElement("textarea");
+        this._value = "";
+        this.value = this.getAttribute("value") || "";
+        this.previousValue = this.value;
+        this.lastChange = new Date();
+        this.changed = false;
+    }
 
-    this.container = document.createElement('div');
-    this.container.classList.add('chat-input');
-    this.container.innerHTML = `
-      <textarea placeholder="Type a message..." rows="2"></textarea>
-      <upload-button></upload-button>
-    `;
-    this.textBox = this.container.querySelector('textarea');
-    this.uploadButton = this.container.querySelector('upload-button');
-    this.uploadButton.chatInput = this 
-    this.textBox.addEventListener('input', (e) => {
-      this.dispatchEvent(new CustomEvent('input', { detail: e.target.value, bubbles: true }));
-      const message = e.target.value;
-      const button = this.container.querySelector('button');
-      button.disabled = !message;
-    });
+    get value() {
+        return this._value;
+    }
 
-    this.textBox.addEventListener('change', (e) => {
-      e.preventDefault();
-      this.dispatchEvent(new CustomEvent('change', { detail: e.target.value, bubbles: true }));
-      console.error(e.target.value);
-    });
+    set value(value) {
+        this._value = value || "";
+        this.textarea.value = this._value;
+    }
 
-    this.textBox.addEventListener('keydown', (e) => {
-      if (e.key === 'Enter' && !e.shiftKey) {
-        e.preventDefault();
-        const message = e.target.value.trim();
-        if (!message) return;
-        this.dispatchEvent(new CustomEvent('submit', { detail: message, bubbles: true }));
-        e.target.value = '';
-      }
-    });
+    resolveAutoComplete() {
+        let count = 0;
+        let value = null;
+        Object.keys(this.autoCompletions).forEach((key) => {
+            if (key.startsWith(this.value)) {
+                count++;
+                value = key;
+            }
+        });
+        if (count == 1)
+            return value;
+        return null;
+    }
 
-    this.component.appendChild(this.container);
-  }
+    isActive() {
+        return document.activeElement === this.textarea;
+    }
+
+    focus() {
+        this.textarea.focus();
+    }
+
+    connectedCallback() {
+        this.liveType = this.getAttribute("live-type") === "true";
+        this.liveTypeInterval = parseInt(this.getAttribute("live-type-interval")) || 3;
+        this.channelUid = this.getAttribute("channel");
+        this.messageUid = null;
+
+        this.classList.add("chat-input");
+
+        this.textarea.setAttribute("placeholder", "Type a message...");
+        this.textarea.setAttribute("rows", "2");
+
+        this.appendChild(this.textarea);
+
+        this.uploadButton = document.createElement("upload-button");
+        this.uploadButton.setAttribute("channel", this.channelUid);
+        this.uploadButton.addEventListener("upload", (e) => {
+            this.dispatchEvent(new CustomEvent("upload", e));
+        });
+        this.uploadButton.addEventListener("uploaded", (e) => {
+            this.dispatchEvent(new CustomEvent("uploaded", e));
+        });
+
+        this.appendChild(this.uploadButton);
+
+        this.textarea.addEventListener("keyup", (e) => {
+            if(e.key === 'Enter' && !e.shiftKey) {
+                this.value = ''
+                e.target.value = '';
+                return 
+            }
+            this.value = e.target.value;
+            this.changed = true;
+            this.update();
+        });
+
+        this.textarea.addEventListener("keydown", (e) => {
+            this.value = e.target.value;
+            if (e.key === "Tab") {
+                e.preventDefault();
+                let autoCompletion = this.resolveAutoComplete();
+                if (autoCompletion) {
+                    e.target.value = autoCompletion;
+                    this.value = autoCompletion;
+                    return;
+                }
+            }
+            if (e.key === 'Enter' && !e.shiftKey) {
+                e.preventDefault();
+
+                const message = e.target.value;
+                this.messageUid = null;
+                this.value = '';
+                this.previousValue = '';
+
+                if (!message) {
+                    return;
+                }
+
+                let autoCompletion = this.autoCompletions[message];
+                if (autoCompletion) {
+                    this.value = '';
+                    this.previousValue = '';
+                    e.target.value = '';
+                    autoCompletion();
+                    return;
+                }
+
+                e.target.value = '';
+                this.value = '';
+                this.messageUid = null;
+                this.sendMessage(this.channelUid, message).then((uid) => {
+                    this.messageUid = uid;
+                });
+            }
+        });
+
+        this.changeInterval = setInterval(() => {
+            if (!this.liveType) {
+                return;
+            }
+            if (this.value !== this.previousValue) {
+                if (this.trackSecondsBetweenEvents(this.lastChange, new Date()) >= this.liveTypeInterval) {
+                    this.value = '';
+                    this.previousValue = '';
+                }
+                this.lastChange = new Date();
+            }
+            this.update();
+        }, 300);
+
+        this.addEventListener("upload", (e) => {
+            this.focus();
+        });
+        this.addEventListener("uploaded", function (e) {
+            let message = "";
+            e.detail.files.forEach((file) => {
+                message += `[${file.name}](/channel/attachment/${file.relative_url})`;
+            });
+            app.rpc.sendMessage(this.channelUid, message);
+        });
+    }
+
+    trackSecondsBetweenEvents(event1Time, event2Time) {
+        const millisecondsDifference = event2Time.getTime() - event1Time.getTime();
+        return millisecondsDifference / 1000;
+    }
+
+    newMessage() {
+        if (!this.messageUid) {
+            this.messageUid = '?';
+        }
+
+        this.sendMessage(this.channelUid, this.value).then((uid) => {
+            this.messageUid = uid;
+        });
+    }
+
+    updateMessage() {
+        if (this.value[0] == "/") {
+            return false;
+        }
+        if (!this.messageUid) {
+            this.newMessage();
+            return false;
+        }
+        if (this.messageUid === '?') {
+            return false;
+        }
+        if (typeof app !== "undefined" && app.rpc && typeof app.rpc.updateMessageText === "function") {
+            app.rpc.updateMessageText(this.messageUid, this.value);
+        }
+    }
+
+    updateStatus() {
+        if (this.liveType) {
+            return;
+        }
+        if (this.trackSecondsBetweenEvents(this.lastUpdateEvent, new Date()) > 1) {
+            this.lastUpdateEvent = new Date();
+            if (typeof app !== "undefined" && app.rpc && typeof app.rpc.set_typing === "function") {
+                app.rpc.set_typing(this.channelUid);
+            }
+        }
+    }
+
+    update() {
+        const expired = this.trackSecondsBetweenEvents(this.lastChange, new Date()) >= this.liveTypeInterval;
+        const changed = (this.value !== this.previousValue);
+
+        if (changed || expired) {
+            this.lastChange = new Date();
+            this.updateStatus();
+        }
+
+        this.previousValue = this.value;
+
+        if (this.liveType && expired) {
+            this.value = "";
+            this.previousValue = "";
+            this.messageUid = null;
+            return;
+        }
+
+        if (changed) {
+            if (this.liveType) {
+                this.updateMessage();
+            }
+        }
+    }
+
+    async sendMessage(channelUid, value) {
+        if (!value.trim()) {
+            return null;
+        }
+        return await app.rpc.sendMessage(channelUid, value);
+    }
 }
 
-customElements.define('chat-input', ChatInputElement);
+customElements.define('chat-input', ChatInputComponent);
diff --git a/src/snek/templates/app.html b/src/snek/templates/app.html
index 7baa67a..596b5d1 100644
--- a/src/snek/templates/app.html
+++ b/src/snek/templates/app.html
@@ -18,6 +18,7 @@
   <script src="/file-manager.js" type="module"></script>
   <script src="/user-list.js"></script>
   <script src="/message-list.js" type="module"></script>
+  <script src="/chat-input.js" type="module"></script>
   <link rel="stylesheet" href="/user-list.css">
 
   <link rel="stylesheet" href="/base.css">
diff --git a/src/snek/templates/web.html b/src/snek/templates/web.html
index 02e60df..94d0ac5 100644
--- a/src/snek/templates/web.html
+++ b/src/snek/templates/web.html
@@ -4,10 +4,6 @@
 
 {% block main %}
 
-
-
-
-
 <section class="chat-area">
     <message-list class="chat-messages">
     {% for message in messages %}
@@ -16,10 +12,7 @@
         {% endautoescape %}
     {% endfor %}
     </message-list>
-    <div class="chat-input">
-        <textarea list="chat-input-autocomplete-items" placeholder="Type a message..." rows="2" autocomplete="on"></textarea>
-        <upload-button channel="{{ channel.uid.value }}"></upload-button>
-    </div>
+    <chat-input live-type="false" channel="{{ channel.uid.value }}"></chat-input>
 </section>
 {% include "dialog_help.html" %}
 {% include "dialog_online.html" %}
@@ -27,11 +20,8 @@
     import { app } from "/app.js";
     import { Schedule } from "/schedule.js";
     const channelUid = "{{ channel.uid.value }}";
-
-    function getInputField(){
-        return document.querySelector("textarea")
-    }
-    getInputField().autoComplete = {
+    const chatInputField = document.querySelector("chat-input");
+    chatInputField.autoCompletions = {
         "/online": () =>{
             showOnline();
         },
@@ -39,117 +29,15 @@
             document.querySelector(".chat-messages").innerHTML = '';
         },
         "/live": () =>{
-            getInputField().liveType = !getInputField().liveType
+            
+            chatInputField.liveType = !chatInputField.liveType
         },
         "/help": () => {
             showHelp();
         }
-    }
-
-
-    function initInputField(textBox) {
-        if(textBox.liveType == undefined){
-            textBox.liveType = false
-        }
-        let typeTimeout = null;
-        textBox.addEventListener('keydown',async (e) => {
-            if(typeTimeout){
-                clearTimeout(typeTimeout)
-                typeTimeout = null
-            }
-            if(e.target.liveType){
-                typeTimeout = setTimeout(()=>{
-                    e.target.lastMessageUid = null
-                    e.target.value = ''
-                },3000)
-            }
-            if(e.key === "ArrowUp"){
-                const value = findDivAboveText(e.target.value).querySelector('.text')
-                e.target.value = value.textContent
-                console.info("HIERR")
-                return
-            }
-            if (e.key === "Tab") {
-
-                const message = e.target.value.trim();
-                if (!message) {
-                    return
-                }
-                let autoCompleteHandler = null;
-                Object.keys(e.target.autoComplete).forEach((key)=>{
-                    if(key.startsWith(message)){
-                        if(autoCompleteHandler){
-                            return 
-                        }
-                        autoCompleteHandler = key
-                    }
-                })
-                if(autoCompleteHandler){
-                    e.preventDefault();
-                    e.target.value = autoCompleteHandler;
-                    return
-                }
-            }
-            if (e.key === 'Enter' && !e.shiftKey) {
-                e.preventDefault();
-                const message = e.target.value.trim();
-                if (!message) {
-                    return
-                }
-                let autoCompleteHandler = e.target.autoComplete[message]
-                if(autoCompleteHandler){
-                    const value = message;
-                    e.target.value = '';
-                    autoCompleteHandler(value)
-                    return
-                }
-
-                e.target.value = '';
-                if(textBox.liveType && textBox.lastMessageUid && textBox.lastMessageUid != '?'){
-                        
-                
-                    app.rpc.updateMessageText(textBox.lastMessageUid, message)
-                textBox.lastMessageUid = null
-                    return 
-                }
-
-                const messageResponse = await app.rpc.sendMessage(channelUid, message);
-                
-	    }else{
-		if(textBox.liveType){
-            if(e.target.value.endsWith("\n") || e.target.value.endsWith(" ")){
-                return
-            } 
-            if(e.target.value[0] == "/"){
-                return
-            }
-            if(!textBox.lastMessageUid){
-                textBox.lastMessageUid = '?'
-                app.rpc.sendMessage(channelUid, e.target.value).then((messageResponse)=>{
-                    textBox.lastMessageUid = messageResponse
-                })
-            }
-            if(textBox.lastMessageUid == '?'){
-                return;
-            }
-            app.rpc.updateMessageText(textBox.lastMessageUid, e.target.value)
-        }else{
-            app.rpc.set_typing(channelUid)
-        }
-
-        
-	    }
-        });
-        document.querySelector("upload-button").addEventListener("upload",function(e){
-            getInputField().focus();
-        })
-        document.querySelector("upload-button").addEventListener("uploaded",function(e){
-            let message = ""
-            e.detail.files.forEach((file)=>{
-                message += `[${file.name}](/channel/attachment/${file.relative_url})`
-            })
-            app.rpc.sendMessage(channelUid,message)
-        })
+     }
+    
+        const textBox = document.querySelector("chat-input").textarea
         textBox.addEventListener("paste", async (e) => {
             try {
                 const clipboardItems = await navigator.clipboard.read();
@@ -168,7 +56,7 @@
                 }
 
                 if (dt.items.length > 0) {
-                    const uploadButton = document.querySelector("upload-button");
+                    const uploadButton = chatInputField.uploadButton
                     const input = uploadButton.shadowRoot.querySelector('.file-input')
                     input.files = dt.files;
 
@@ -187,7 +75,7 @@
 
             const dt = e.dataTransfer;
             if (dt.items.length > 0) {
-                const uploadButton = document.querySelector("upload-button");
+                const uploadButton = chatInputField.uploadButton
                 const input = uploadButton.shadowRoot.querySelector('.file-input')
                 input.files = dt.files;
 
@@ -197,13 +85,16 @@
         chatInput.addEventListener("dragover", async (e) => {
             e.preventDefault();
             e.dataTransfer.dropEffect = "link";
+        
+
         })
 
-        textBox.focus();
-    }
+            chatInputField.textarea.focus();
+
+    
 
     function replyMessage(message) {
-        const field = getInputField()
+        const field = chatInputField
         field.value = "```markdown\n> " + (message || '') + "\n```\n";
         field.focus();
     }
@@ -294,8 +185,8 @@
         lastMessage = messagesContainer.querySelector(".message:last-child");
         if (doScrollDown) {
             lastMessage?.scrollIntoView({ block: "end", inline: "nearest" });
-             const inputBox = document.querySelector(".chat-input");
-             inputBox.scrollIntoView({ block: "end", inline: "nearest" });
+             
+             chatInputField.scrollIntoView({ block: "end", inline: "nearest" });
         }
     }
 
@@ -378,17 +269,17 @@
             messagesContainer.querySelector(".message:last-child").scrollIntoView({ block: "end", inline: "nearest" });
                 setTimeout(() => {
                     
-                    getInputField().focus();
+                    chatInputField.focus();
                 },500)
 
             }
         }
         if (event.shiftKey && event.key === 'G') {
-            if(document.activeElement != getInputField()){
+            if(chatInputField.isActive()){
             
                 updateLayout(true);
                 setTimeout(() => {
-                    getInputField().focus();
+                    chatInputField.focus();
                 },500)
             }
 
@@ -432,7 +323,6 @@
         document.body.removeChild(overlay);
       });
     });
-    initInputField(getInputField());
     updateLayout(true);
 </script>
 {% endblock %}