diff --git a/mo/web/table.py b/mo/web/table.py
index 50a2b0d220af9a1920ef5d6ec2985b18e616bc6a..95995dc2d25990aa44e1e750f123f53ee1ae9810 100644
--- a/mo/web/table.py
+++ b/mo/web/table.py
@@ -71,22 +71,35 @@ class CellLink(Cell):
         return a + '>' + escape(self.text) + '</a>' + str(self.html_suffix)
 
 
-class CellCheckbox(Cell):
+class CellInput(Cell):
     name: str
     value: str
-    checked: bool
+    type: str
+    attrs: Dict[str, Optional[str]]
 
-    def __init__(self, name: str, value: str, checked: bool = False):
+    def __init__(self, name: str, value: str, type: str = "text", attrs: Dict[str, Optional[str]] = {}):
         Cell.__init__(self, "")
         self.name = name
         self.value = value
-        self.checked = checked
+        self.type = type
+        self.attrs = attrs
 
     def to_html(self) -> str:
-        ch = f'<td><input type="checkbox" name="{self.name}" value="{self.value}"'
-        if self.checked:
-            ch += ' checked'
-        return ch + '>'
+        out = f'<td><input type="{self.type}" name="{self.name}" value="{self.value}"'
+        for (attr, value) in self.attrs.items():
+            out += f' {attr}'
+            if value is not None:
+                out += '=' + escape(str(value))
+        return out + '>'
+
+
+class CellCheckbox(CellInput):
+    def __init__(self, name: str, value: str, checked: bool = False):
+        attrs = {}
+        if checked:
+            attrs['checked'] = None
+        CellInput.__init__(self, name, value, "checkbox", attrs)
+
 
 class CellMarkup(Cell):
     text: str