diff --git a/scripts/.pylintrc b/scripts/.pylintrc
index 43b2b9bbb126b33d46a4ce3faab76ffb6c1b2b72..bfe23475ab2d623952655a3aa4d8870cb47423f7 100644
--- a/scripts/.pylintrc
+++ b/scripts/.pylintrc
@@ -141,7 +141,8 @@ disable=print-statement,
         comprehension-escape,
         E0611,
         I1101,
-        E1101
+        E1101,
+        R0901
 
 # Enable the message, report, category or checker with the given id(s). You can
 # either give multiple identifier separated by comma (,) or put this option
diff --git a/soitool/modules/module_base.py b/soitool/modules/module_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..92e7e6bc6145a1a1921cb5580c7b8384d77cad51
--- /dev/null
+++ b/soitool/modules/module_base.py
@@ -0,0 +1,14 @@
+"""Base/interface of each module."""
+from abc import ABC
+
+
+class ModuleBase(ABC):
+    """Interface for SOI-modules."""
+
+    def get_size(self):
+        """Abstract method, should be implemented by derived class."""
+        raise NotImplementedError
+
+    def render_onto_pdf(self):
+        """Abstract method, should be implemented by derived class."""
+        raise NotImplementedError
diff --git a/soitool/modules/table_module.py b/soitool/modules/table_module.py
new file mode 100644
index 0000000000000000000000000000000000000000..50a68289d164a6f0a6ed99d02250ca0a561c6413
--- /dev/null
+++ b/soitool/modules/table_module.py
@@ -0,0 +1,128 @@
+"""Module containing subclassed SOIModule (QTableWidget, ModuleBase)."""
+from PySide2.QtWidgets import QTableWidget, QTableWidgetItem, QShortcut
+from PySide2 import QtGui
+from module_base import ModuleBase
+
+HEADER_FONT = QtGui.QFont()
+HEADER_FONT.setFamily('Arial')
+HEADER_FONT.setPointSize(12)
+HEADER_FONT.setWeight(100)
+
+
+class Meta(type(ModuleBase), type(QTableWidget)):
+    """Used as a metaclass to enable multiple inheritance."""
+
+
+class TableModule(ModuleBase, QTableWidget, metaclass=Meta):
+    """Modified QTableWidget.
+
+    Has shortcuts for adding and removing rows and columns.
+    Also inherits functions from ModuleBase, which is implemented by this class.
+    """
+
+    def __init__(self):
+        """Initialize QTableWidget."""
+        QTableWidget.__init__(self)
+        super(QTableWidget)
+
+        self.horizontalHeader().hide()
+        self.verticalHeader().hide()
+
+        self.setColumnCount(2)
+        self.setRowCount(2)
+
+        self.set_header_item(0, "")
+        self.set_header_item(1, "")
+
+        self.resizeRowsToContents()
+        self.resizeColumnsToContents()
+
+        self.cellChanged.connect(self.cell_changed)
+
+        self.set_shortcuts()
+
+    def set_header_item(self, column, text):
+        """Insert item with header-style.
+
+        Item will always be set on header row (top row).
+
+        Parameters
+        ----------
+        column : int
+            What column index for inserting item.
+        text : String
+            What text the item should contain.
+        """
+        item = QTableWidgetItem(text)
+        item.setBackground(QtGui.QBrush(QtGui.QColor(220, 220, 220)))
+        item.setFont(HEADER_FONT)
+        self.setItem(0, column, item)
+
+    def set_shortcuts(self):
+        """Set shortcuts for adding and removing rows and columns."""
+        shortcut_add_col = QShortcut(QtGui.QKeySequence("Shift++"), self)
+        shortcut_rem_col = QShortcut(QtGui.QKeySequence("Shift+-"), self)
+        shortcut_add_row = QShortcut(QtGui.QKeySequence("Ctrl++"), self)
+        shortcut_rem_row = QShortcut(QtGui.QKeySequence("Ctrl+-"), self)
+
+        shortcut_add_col.activated.connect(self.add_column)
+        shortcut_rem_col.activated.connect(self.remove_column)
+        shortcut_add_row.activated.connect(self.add_row)
+        shortcut_rem_row.activated.connect(self.remove_row)
+
+    def add_column(self):
+        """Add column to the right of selected column."""
+        self.insertColumn(self.columnCount())
+
+        # From selected column, move all columns one step to the right
+        to_pos = self.columnCount() - 2  # to_pos is target index for columns
+        while to_pos > self.currentColumn():
+            # Loop through all rows
+            for j in range(self.rowCount()):
+                item = self.takeItem(j, to_pos)
+                self.setItem(j, to_pos + 1, item)
+            to_pos -= 1
+
+        self.set_header_item(self.currentColumn() + 1, "")
+        self.resizeColumnsToContents()
+
+    def remove_column(self):
+        """Remove selected column if two or more columns exist."""
+        if self.columnCount() > 1:
+            self.removeColumn(self.currentColumn())
+
+    def add_row(self):
+        """Add row below selected row."""
+        self.insertRow(self.currentRow() + 1)
+        self.resizeRowsToContents()
+
+    def remove_row(self):
+        """Remove selected row if two or more rows exist (including header)."""
+        if self.rowCount() > 2:
+            self.removeRow(self.currentRow())
+
+    def cell_changed(self):
+        """Resize rows and columns to contents when a cell changes."""
+        self.resizeColumnsToContents()
+        self.resizeRowsToContents()
+
+    def get_size(self):
+        """Get size of widget.
+
+        Returns
+        -------
+        Tuple
+            (width, height) (total)
+        """
+        width = 0
+        for i in range(self.columnCount()):
+            width += self.columnWidth(i)
+
+        height = 0
+        for i in range(self.columnCount()):
+            height += self.rowHeight(i)
+
+        return width, height
+
+    def render_onto_pdf(self):
+        """Render onto pdf."""