diff --git a/silkaj/tui.py b/silkaj/tui.py
index 99a3dec81b8303e4d8101d30974b60e20b96df0d..b5d757765f24166e368b10ce3bb85473968b7283 100644
--- a/silkaj/tui.py
+++ b/silkaj/tui.py
@@ -13,15 +13,21 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with Silkaj. If not, see <https://www.gnu.org/licenses/>.
 
+import shutil
 import sys
+from collections import OrderedDict
 from typing import List, Optional
 
 import click
+from texttable import Texttable
 
 from silkaj import constants
 from silkaj import crypto_tools as ct
 from silkaj import wot_tools
 
+HORIZ_TABLE_CHARS = ["─", "│", "─", "═"]
+VERT_TABLE_CHARS = ["─", "│", "│", "═"]
+
 
 def display_amount(
     tx: List, message: str, amount: float, ud_value: float, currency_symbol: str
@@ -66,3 +72,74 @@ def gen_pubkey_checksum(
 def send_doc_confirmation(document_name: str) -> None:
     if not click.confirm(f"Do you confirm sending this {document_name}?"):
         sys.exit(constants.SUCCESS_EXIT_STATUS)
+
+
+class Table(Texttable):
+    def __init__(
+        self,
+        style="default",
+    ):
+        Texttable.__init__(self, max_width=shutil.get_terminal_size()[0])
+
+        if style == "columns":
+            self.set_deco(self.HEADER | self.VLINES | self.BORDER)
+        self.set_chars(VERT_TABLE_CHARS)
+
+    def fill_rows(self, rows: list, headers=None):
+        """
+        Fills a table from headers and rows list.
+        `rows` is a list of lists representing each row content.
+        each element of `rows` and headers must be of same length.
+        """
+        if headers:
+            if len(rows) == 0:
+                rows.append([""] * len(headers))
+            assert len(headers) == len(rows[0])
+            self.header(headers)
+        for line in rows:
+            assert len(line) == len(rows[0])
+            self.add_row(line)
+        return self
+
+    def fill_from_dict(self, _dict: OrderedDict):
+        """
+        Given an OrderedDict where each value represents a column,
+        fill a table where labels are dict keys and columns are dict values.
+        This function stops on the first line with only empty cells.
+        """
+        labels = list(_dict.keys())
+        content = []
+
+        n = 0
+        while True:
+            line = []
+            empty_cells_number = 0
+
+            for label in labels:
+                try:
+                    line.append(_dict[label][n])
+                except IndexError:
+                    line.append("")
+                    empty_cells_number += 1
+                # break on first empty line
+            if empty_cells_number == len(labels):
+                break
+            content.append(line)
+            n += 1
+
+        return self.fill_rows(content, labels)
+
+    def fill_from_dict_list(self, dict_list: list):
+        """
+        Given a list of dict with same keys,
+        fills the table with keys as headers.
+        """
+        headers = list(dict_list[0].keys())
+        content = []
+        for _dict in dict_list:
+            assert list(_dict.keys()) == headers
+            line = []
+            for head in headers:
+                line.append(_dict[head])
+            content.append(line)
+        return self.fill_rows(content, headers)
diff --git a/tests/test_tui.py b/tests/test_tui.py
index e488e3090f01af9659158f729b70129228288856..72924fc86ab629cfbec547f5ba889a4f09efca64 100644
--- a/tests/test_tui.py
+++ b/tests/test_tui.py
@@ -13,13 +13,16 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with Silkaj. If not, see <https://www.gnu.org/licenses/>.
 
+import shutil
+from collections import OrderedDict
+
 import pytest
+from texttable import Texttable
 
 from patched.test_constants import mock_ud_value
 from patched.wot import patched_is_member
-from silkaj import wot_tools
+from silkaj import tui, wot_tools
 from silkaj.constants import G1_SYMBOL, SHORT_PUBKEY_SIZE
-from silkaj.tui import display_amount, display_pubkey, gen_pubkey_checksum
 
 
 # display_amount()
@@ -35,7 +38,7 @@ def test_display_amount(message, amount, currency_symbol):
         ]
     ]
     tx = []
-    display_amount(tx, message, amount, mock_ud_value, currency_symbol)
+    tui.display_amount(tx, message, amount, mock_ud_value, currency_symbol)
     assert tx == expected
 
 
@@ -50,11 +53,11 @@ def test_display_amount(message, amount, currency_symbol):
 def test_display_pubkey(message, pubkey, uid, monkeypatch):
     monkeypatch.setattr(wot_tools, "is_member", patched_is_member)
 
-    expected = [[f"{message} (pubkey:checksum)", gen_pubkey_checksum(pubkey)]]
+    expected = [[f"{message} (pubkey:checksum)", tui.gen_pubkey_checksum(pubkey)]]
     if uid:
         expected.append([f"{message} (id)", uid])
     tx = []
-    display_pubkey(tx, message, pubkey)
+    tui.display_pubkey(tx, message, pubkey)
     assert tx == expected
 
 
@@ -66,10 +69,138 @@ def test_display_pubkey(message, pubkey, uid, monkeypatch):
     ],
 )
 def test_gen_pubkey_checksum(pubkey, checksum):
-    assert pubkey + ":" + checksum == gen_pubkey_checksum(pubkey)
-    assert pubkey[:SHORT_PUBKEY_SIZE] + "…:" + checksum == gen_pubkey_checksum(
+    assert f"{pubkey}:{checksum}" == tui.gen_pubkey_checksum(pubkey)
+    assert f"{pubkey[:SHORT_PUBKEY_SIZE]}…:{checksum}" == tui.gen_pubkey_checksum(
         pubkey, short=True
     )
-    assert pubkey[:14] + "…:" + checksum == gen_pubkey_checksum(
+    assert f"{pubkey[:14]}…:{checksum}" == tui.gen_pubkey_checksum(
         pubkey, short=True, length=14
     )
+
+
+def test_create_table():
+
+    expected = Texttable(max_width=shutil.get_terminal_size()[0])
+    expected.add_rows([["one", "two"], ["three", "four"]])
+    expected.set_chars(tui.VERT_TABLE_CHARS)
+
+    test_table = tui.Table().add_rows([["one", "two"], ["three", "four"]])
+    assert expected.draw() == test_table.draw()
+
+    expected.set_deco(expected.HEADER | expected.VLINES | expected.BORDER)
+    test_table = tui.Table("columns").add_rows([["one", "two"], ["three", "four"]])
+    assert expected.draw() == test_table.draw()
+
+
+@pytest.mark.parametrize(
+    "rows, headers, expected",
+    [
+        (
+            [
+                ["three", "o'clock"],
+                ["four", "o'clock"],
+                ["rock", "rock around the clock"],
+            ],
+            ["one", "two"],
+            "│───────│───────────────────────│\n\
+│  one  │          two          │\n\
+│═══════│═══════════════════════│\n\
+│ three │ o'clock               │\n\
+│───────│───────────────────────│\n\
+│ four  │ o'clock               │\n\
+│───────│───────────────────────│\n\
+│ rock  │ rock around the clock │\n\
+│───────│───────────────────────│",
+        ),
+        (
+            [
+                ["three", "o'clock"],
+                ["four", "o'clock"],
+                ["rock", "rock around the clock"],
+            ],
+            None,
+            "│───────│───────────────────────│\n\
+│ three │ o'clock               │\n\
+│───────│───────────────────────│\n\
+│ four  │ o'clock               │\n\
+│───────│───────────────────────│\n\
+│ rock  │ rock around the clock │\n\
+│───────│───────────────────────│",
+        ),
+        (
+            [
+                ["three", "o'clock"],
+                ["four", "o'clock"],
+                ["rock"],
+            ],
+            ["one", "two"],
+            False,
+        ),
+    ],
+)
+def test_fill_rows(rows, headers, expected):
+    table = tui.Table()
+    if not expected:
+        with pytest.raises(AssertionError) as pytest_error:
+            table.fill_rows(rows, headers)
+        assert pytest_error.type == AssertionError  # is it useful ?
+    else:
+        table.fill_rows(rows, headers)
+        assert table.draw() == expected
+
+
+@pytest.mark.parametrize(
+    "dict_, expected",
+    [
+        (
+            OrderedDict(
+                [
+                    ("one", ["three", "four", "rock"]),
+                    ("two", ["o'clock", "o'clock", "rock around the clock"]),
+                ]
+            ),
+            "│───────│───────────────────────│\n\
+│  one  │          two          │\n\
+│═══════│═══════════════════════│\n\
+│ three │ o'clock               │\n\
+│───────│───────────────────────│\n\
+│ four  │ o'clock               │\n\
+│───────│───────────────────────│\n\
+│ rock  │ rock around the clock │\n\
+│───────│───────────────────────│",
+        ),
+    ],
+)
+def test_fill_from_dict(dict_, expected):
+    table = tui.Table()
+    table.fill_from_dict(dict_)
+    assert table.draw() == expected
+
+
+@pytest.mark.parametrize(
+    "dict_list, expected",
+    [
+        (
+            (
+                [
+                    {"one": "three", "two": "o'clock"},
+                    {"one": "four", "two": "o'clock"},
+                    {"one": "rock", "two": "rock around the clock"},
+                ]
+            ),
+            "│───────│───────────────────────│\n\
+│  one  │          two          │\n\
+│═══════│═══════════════════════│\n\
+│ three │ o'clock               │\n\
+│───────│───────────────────────│\n\
+│ four  │ o'clock               │\n\
+│───────│───────────────────────│\n\
+│ rock  │ rock around the clock │\n\
+│───────│───────────────────────│",
+        ),
+    ],
+)
+def test_fill_from_dict_list(dict_list, expected):
+    table = tui.Table()
+    table.fill_from_dict_list(dict_list)
+    assert table.draw() == expected