diff --git a/invenio_previewer/assets/semantic-ui/scss/invenio_previewer/txt.scss b/invenio_previewer/assets/semantic-ui/scss/invenio_previewer/txt.scss
new file mode 100644
index 0000000000000000000000000000000000000000..61111bc8de627b35858aa7f6c1c471b58b743c16
--- /dev/null
+++ b/invenio_previewer/assets/semantic-ui/scss/invenio_previewer/txt.scss
@@ -0,0 +1,4 @@
+#text-previewer {
+  padding: 0.5em;
+}
+
diff --git a/invenio_previewer/config.py b/invenio_previewer/config.py
index d88c3974fb4c7076653be5d2730664b4e2c89c01..b962a913fbf51f10259a6aa032502ef780296fa9 100644
--- a/invenio_previewer/config.py
+++ b/invenio_previewer/config.py
@@ -26,6 +26,9 @@ PREVIEWER_MAX_FILE_SIZE_BYTES = 1 * 1024 * 1024
 PREVIEWER_MAX_IMAGE_SIZE_BYTES = 0.5 * 1024 * 1024
 """Maximum file size in bytes for image files."""
 
+PREVIEWER_TXT_MAX_BYTES = 1 * 1024 * 1024
+"""Maximum number of .txt file bytes to preview before truncated."""
+
 PREVIEWER_ZIP_MAX_FILES = 1000
 """Max number of files showed in the ZIP previewer."""
 
diff --git a/invenio_previewer/extensions/txt.py b/invenio_previewer/extensions/txt.py
index 7739ad567eeb997bc0d2f47a17cb8c096dc4a833..2df9744d72b5f00450000d94ad6b667ab160bb97 100644
--- a/invenio_previewer/extensions/txt.py
+++ b/invenio_previewer/extensions/txt.py
@@ -8,19 +8,20 @@
 
 """Text rendering."""
 
-from flask import render_template
+from flask import current_app, render_template
 
 from ..proxies import current_previewer
 from ..utils import detect_encoding
 
 previewable_extensions = ["txt"]
+max_bytes = current_app.config.get('PREVIEWER_TXT_MAX_BYTES', -1)
 
 
 def render(file):
     """Render HTML from txt file content."""
     with file.open() as fp:
         encoding = detect_encoding(fp, default="utf-8")
-        return fp.read().decode(encoding)
+        return fp.read(max_bytes).decode(encoding, errors="ignore")
 
 
 def can_preview(file):
@@ -37,5 +38,6 @@ def preview(file):
         file=file,
         content=render(file),
         js_bundles=current_previewer.js_bundles,
-        css_bundles=current_previewer.css_bundles,
+        css_bundles=['txt_css.css'],
+        truncated=max_bytes < file.size,
     )
diff --git a/invenio_previewer/templates/semantic-ui/invenio_previewer/txt.html b/invenio_previewer/templates/semantic-ui/invenio_previewer/txt.html
index 7587a59be5f656968864af8ae07c08bed3fa7560..e3983636e69ea95805f741f86b10f9d92efaeb62 100644
--- a/invenio_previewer/templates/semantic-ui/invenio_previewer/txt.html
+++ b/invenio_previewer/templates/semantic-ui/invenio_previewer/txt.html
@@ -10,11 +10,10 @@
 {%- extends config.PREVIEWER_ABSTRACT_TEMPLATE %}
 
 {% block panel %}
-<div class="ui container">
-    <div class="ui grid column">
-        <div class="column">
-            <pre>{{ content }}</pre>
-        </div>
-    </div>
+<div id="text-previewer">
+    <pre>{{ content }}</pre>
+  {% if truncated %}
+    <hr /><div class="banner">{{ _('Preview of large file truncated') }}</div>
+  {% endif %}
 </div>
 {% endblock %}
diff --git a/invenio_previewer/webpack.py b/invenio_previewer/webpack.py
index a46b68a397e3a2e4150481ec9a32bf87762bd211..66005518179e606ccf97fe01b33b7c75cf88235e 100644
--- a/invenio_previewer/webpack.py
+++ b/invenio_previewer/webpack.py
@@ -75,6 +75,7 @@ previewer = WebpackThemeBundle(
                 'bottom_css': './scss/invenio_previewer/bottom.scss',
                 'simple_image_css':
                     './scss/invenio_previewer/simple_image.scss',
+                'txt_css': './scss/invenio_previewer/txt.scss',
             },
             dependencies={
                 'd3': '^3.5.17',
diff --git a/tests/test_macros.py b/tests/test_macros.py
index 6ff5548e4a6c0d969df3c1ee4824860d08968c08..c265b2d873acad97ffaa4fc87676352828724f80 100644
--- a/tests/test_macros.py
+++ b/tests/test_macros.py
@@ -232,6 +232,24 @@ def test_simple_image_extension(app, webassets, record):
         assert 'class="previewer-simple-image"' in res.get_data(as_text=True)
 
 
+def test_txt_extension(app, webassets, record):
+    """Text .txt file viewer."""
+    create_file(record, 'test1.txt', BytesIO(b'test content foobar'))
+
+    with app.test_client() as client:
+        res = client.get(preview_url(record['control_number'], 'test1.txt'))
+        assert "<pre>test content foobar</pre>" in res.get_data(as_text=True)
+
+    max_file_size = app.config.get(
+        'PREVIEWER_TXT_MAX_BYTES', 1 * 1024 * 1024)
+    too_large_string = '1' * (max_file_size + 1)
+    create_file(record, 'test2.txt', BytesIO(b(too_large_string)))
+
+    with app.test_client() as client:
+        res = client.get(preview_url(record['control_number'], 'test1.txt'))
+        assert "file truncated" in res.get_data(as_text=True)
+
+
 def test_view_macro_file_list(app):
     """Test file list macro."""
     with app.test_request_context():