Skip to content

Tabler UI#2892

Open
mariofix wants to merge 18 commits into
pallets-eco:masterfrom
mariofix:tabler-ui
Open

Tabler UI#2892
mariofix wants to merge 18 commits into
pallets-eco:masterfrom
mariofix:tabler-ui

Conversation

@mariofix

@mariofix mariofix commented May 5, 2026

Copy link
Copy Markdown

This PR adds Tabler UI into Flask-Admin, an early version.

fixes #2443

See tabler folder included in examples

admin = Admin(
    app,
    name="Example: Tabler",
    theme=TablerTheme(layout="condensed")
)

options allowed

layout: TablerLayout = "fluid" # "vertical", "fluid", "condensed"
# Defaults match Tabler's own defaults so existing deployments are unaffected.
theme: str = "light"  # "light" | "dark"
theme_primary: str = "blue"  # "blue" | "lime" | "azure" | "indigo" | …
theme_base: str = "gray"  # "gray" | "neutral" | "slate" | "zinc" | "stone"
theme_font: str = "sans-serif"  # "sans-serif" | "serif" | "monospace" | "comic"
theme_radius: str = "1"  # "0" | "0.5" | "1" | "1.5" | "2"

you can go to https://preview.tabler.io to see combinations

let me know what you think

Comment thread flask_admin/theme.py Outdated
@umpirsky

umpirsky commented May 5, 2026

Copy link
Copy Markdown
Contributor

This is huge! Great work, really appreciate the effort, thanks!

@ElLorans ElLorans added the theme label May 10, 2026
@umpirsky

Copy link
Copy Markdown
Contributor

@ElLorans What are the steps that we need to do to push this to the finish line?

@ElLorans

Copy link
Copy Markdown
Contributor

Replying to my 2 comments and fixing the tests (but this I can do myself)

Comment thread flask_admin/model/typefmt.py
Comment thread flask_admin/theme.py Outdated
@umpirsky

Copy link
Copy Markdown
Contributor

Replying to my 2 comments and fixing the tests (but this I can do myself)

@ElLorans Fixed the tests in mariofix#1 cc @mariofix

Comment thread examples/tabler/files/d1/dummy.txt Outdated
Comment thread examples/tabler/static/d1/afile.txt Outdated
Comment thread flask_admin/templates/tabler/admin/file/list.html
Comment thread examples/tabler/pyproject.toml Outdated
@ElLorans

Copy link
Copy Markdown
Contributor

I see you added some static files in the end. What is missing?

@mariofix

mariofix commented May 22, 2026

Copy link
Copy Markdown
Author

I see you added some static files in the end. What is missing?

Yes for Tabler version 1.4.0, the switch stays to allow the use of CDN if you prefer that way.

I'm already working with the nigthly version of tabler so i should have a fast update when they release a new version.

If you have no more comments, this pr is ready.

-- Edit --
On second thought, i'm not happy with this
https://github.com/pallets-eco/flask-admin/pull/2892/changes#diff-0805c14613538db9207ae91ee38f3f7702ec9b5dae7a1a58f0feaf2af0d3df0dR42-R45

   <script>
    window.addEventListener('error', function(e) {
      var suppress = [
        'helpers',
        'form',
        'actions'
      ];
      if (e.filename && suppress.some(function(f) { return e.filename.includes(f); })) {
        e.preventDefault();
        console.warn('Suppressed error in ' + e.filename + ':', e.message);
      }
    }, true);
  </script>

this code silences errors from the files loaded by flask-admin that area made for bootstrap4, this is a dirty fix to allow the js for tabler to work

@ElLorans

Copy link
Copy Markdown
Contributor

If you already added all static files, maybe we can remove the option of using the CDN? What is the advantage? I was proposing to have the first PR without static assets to simplify the review, but if you add them, I don't think we need the CDN anymore.

@mariofix

Copy link
Copy Markdown
Author

If you already added all static files, maybe we can remove the option of using the CDN? What is the advantage? I was proposing to have the first PR without static assets to simplify the review, but if you add them, I don't think we need the CDN anymore.

remove that switch then?

@ElLorans

ElLorans commented May 22, 2026

Copy link
Copy Markdown
Contributor

-- Edit -- On second thought, i'm not happy with this https://github.com/pallets-eco/flask-admin/pull/2892/changes#diff-0805c14613538db9207ae91ee38f3f7702ec9b5dae7a1a58f0feaf2af0d3df0dR42-R45

   <script>
    window.addEventListener('error', function(e) {
      var suppress = [
        'helpers',
        'form',
        'actions'
      ];
      if (e.filename && suppress.some(function(f) { return e.filename.includes(f); })) {
        e.preventDefault();
        console.warn('Suppressed error in ' + e.filename + ':', e.message);
      }
    }, true);
  </script>

this code silences errors from the files loaded by flask-admin that area made for bootstrap4, this is a dirty fix to allow the js for tabler to work

Ideally we do not load these files at all. Let me know if you want to do it or if we can assist, you already did an amazing job.
EDIT: the code is commented out, so I am not sure I understand

Comment thread flask_admin/templates/tabler/admin/base.html Outdated
@ElLorans

Copy link
Copy Markdown
Contributor

I am testing the examples with both themes.

We might have some minor graphical tweaks to do.

  1. Auth (all good, only because the example overrides the master template assuming Bootstrap4):
    Boostrap shows the caret next to the email
image

Tabler shows no left margin and the caret is too close (only due to a non-compatible overriding in the example):
image

  1. Auth-flask-login (part of the issue should again only be due to overriding the master template):
    Left border is now fine (showing that it was not an issue in Auth, since here we are using a more compatible overriding) but edit list shows icons centered rather than left aligned, which is arguably uglier:
image image
  1. Babel:
    Here there is no overriding the .html, so should work fine.
    I notice a scroll bar that should not appear?
image image
  1. Datetime-timezone: All good except the usual scroll bar.
image image
  1. Form Files Images: all good
image image

Comment thread flask_admin/theme.py
TablerLayout = t.Literal["vertical", "fluid", "condensed"]


def _validate_choice(value: str, choices: tuple[str, ...]) -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asking for your honest opinion here: after the t.Literal, do we still need this or is the type hint enough?

mariofix and others added 8 commits May 23, 2026 13:56
Merged from mariofix/flask-admin-tablerui
Tabler version 1.4.0
…rts --disable-error-code name-defined --no-warn-unused-ignores --allow-subclassing-any examples

examples/tabler/main.py:73: error: Missing type parameters for generic type "dict"  [type-arg]
        meta_data: Mapped[dict] = mapped_column(JSON, default=dict, server_default=text("'{}'"))
                          ^
Found 1 error in 1 file (checked 55 source files)
typing: exit 1 (23.45 seconds) /home/umpirsky/Projects/flask-admin> mypy --python-version 3.14 --ignore-missing-imports --disable-error-code name-defined --no-warn-unused-ignores --allow-subclassing-any examples pid=4036247
  typing: FAIL code 1 (72.73=setup[2.26]+cmd[23.15,23.88,23.45] seconds)
  evaluation failed :( (72.82 seconds)
style: venv> .venv/bin/uv venv -p /home/umpirsky/Projects/flask-admin/.venv/bin/python --allow-existing --python-preference system /home/umpirsky/Projects/flask-admin/.tox/style
style: uv-sync> uv sync --locked --python-preference system --extra all --no-default-groups --no-editable --reinstall-package Flask-Admin --group pre-commit -p /home/umpirsky/Projects/flask-admin/.venv/bin/python
style: commands[0]> pre-commit run --all-files
[INFO] Initializing environment for https://github.com/astral-sh/ruff-pre-commit.
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Installing environment for https://github.com/astral-sh/ruff-pre-commit.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Using pre-commit with uv 0.11.6 via pre-commit-uv 4.2.0
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Using pre-commit with uv 0.11.6 via pre-commit-uv 4.2.0
ruff.....................................................................Failed
- hook id: ruff
- exit code: 1
- files were modified by this hook

examples/tabler/main.py:69:89: E501 Line too long (102 > 88)
   |
67 |     title: Mapped[str] = mapped_column(String(64))
68 |     content: Mapped[Text] = mapped_column(Text)
69 |     meta_data: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, server_default=text("'{}'"))
   |                                                                                         ^^^^^^^^^^^^^^ E501
70 |
71 |     def __repr__(self):
   |

examples/tabler/main.py:178:89: E501 Line too long (89 > 88)
    |
176 |     # admin.add_menu_item(MenuDivider(), target_category="Links")
177 |     # admin.add_link(
178 |     #     MenuLink(name="External link", url="http://www.example.com/", category="Links")
    |                                                                                         ^ E501
179 |     # )
    |

flask_admin/tests/tabler/test_renders.py:64:89: E501 Line too long (92 > 88)
   |
62 |             ],
63 |         ),
64 |         ("condensed", ["data-tabler-layout=\"condensed\"", "tabler-admin-navbar-collapse"]),
   |                                                                                         ^^^^ E501
65 |     ],
66 | )
   |

Fixed 7 errors:
- examples/tabler/main.py:
    5 × F401 (unused-import)
    1 × I001 (unsorted-imports)
- flask_admin/tests/tabler/test_renders.py:
    1 × I001 (unsorted-imports)

Found 10 errors (7 fixed, 3 remaining).

ruff-format..............................................................Failed
- hook id: ruff-format
- files were modified by this hook

4 files reformatted, 167 files left unchanged

check for merge conflicts................................................Passed
debug statements (python)................................................Passed
fix utf-8 byte order marker..............................................Passed
trim trailing whitespace.................................................Passed
fix end of files.........................................................Failed
- hook id: end-of-file-fixer
- exit code: 1
- files were modified by this hook

Fixing flask_admin/tests/tabler/__init__.py

style: exit 1 (10.35 seconds) /home/umpirsky/Projects/flask-admin> pre-commit run --all-files pid=4052435
  style: FAIL code 1 (12.66=setup[2.31]+cmd[10.35] seconds)
  evaluation failed :( (12.72 seconds)
@ElLorans

ElLorans commented May 24, 2026

Copy link
Copy Markdown
Contributor

Remaining issues:

  1. Very minor, can be addressed in a separate PR: In vertical layout, sidebar always shows vertical navbar pseudo-element styling
  2. FileAdmin not working
image 3. `column_editable_list` not working. #2847 could help? I would appreciate @hasansezertasan opinion on how to proceed.

@umpirsky

Copy link
Copy Markdown
Contributor

If you already added all static files, maybe we can remove the option of using the CDN? What is the advantage? I was proposing to have the first PR without static assets to simplify the review, but if you add them, I don't think we need the CDN anymore.

I prefer CDN for my use case. I use this on embedded hardware and want to minimize traffic.

@ElLorans

Copy link
Copy Markdown
Contributor

Column editable list works in #2910, so we only need to fix fileadmin.

@mariofix

Copy link
Copy Markdown
Author

Ideally we do not load these files at all. Let me know if you want to do it or if we can assist, you already did an amazing job. EDIT: the code is commented out, so I am not sure I understand

I haven't had much time this week, i commented out to try to fix it within flask-admin, i assume filters don't work in this PR, and there should be errors in the javascript console.
I'll work on this again this weekend

Copilot AI review requested due to automatic review settings June 10, 2026 04:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds an experimental Tabler-based theme to Flask-Admin, including templates/assets, icon support, tests, and an example app to demonstrate configuration.

Changes:

  • Introduce TablerTheme in flask_admin/theme.py with layout + theme attribute options.
  • Add Tabler template set + supporting JS/CSS for modals, filters, actions, and pages.
  • Add Tabler icon type constant/docs, update boolean formatter styling, add tests + a runnable example.

Reviewed changes

Copilot reviewed 42 out of 51 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
flask_admin/theme.py Adds TablerTheme dataclass + layout validation and theme option fields.
flask_admin/tests/tabler/test_renders.py Adds render tests asserting layout markers and table CSS for Tabler templates.
flask_admin/templates/tabler/admin/static.html Adds a theme-local static URL macro for Tabler templates.
flask_admin/templates/tabler/admin/rediscli/response.html Adds Tabler Redis CLI response rendering template.
flask_admin/templates/tabler/admin/rediscli/console.html Adds Tabler Redis CLI console page template.
flask_admin/templates/tabler/admin/model/row_actions.html Adds Tabler row action macros (view/edit/delete).
flask_admin/templates/tabler/admin/model/modals/edit.html Adds Tabler modal edit template.
flask_admin/templates/tabler/admin/model/modals/details.html Adds Tabler modal details template.
flask_admin/templates/tabler/admin/model/modals/create.html Adds Tabler modal create template.
flask_admin/templates/tabler/admin/model/list.html Adds Tabler model list template with filters/actions/modals.
flask_admin/templates/tabler/admin/model/layout.html Adds Tabler model helper macros (filters/search/page size/export).
flask_admin/templates/tabler/admin/model/inline_list_base.html Adds Tabler inline form list rendering base.
flask_admin/templates/tabler/admin/model/inline_form.html Adds Tabler inline form field renderer.
flask_admin/templates/tabler/admin/model/inline_field_list.html Adds Tabler inline field list rendering with errors.
flask_admin/templates/tabler/admin/model/edit.html Adds Tabler model edit page template.
flask_admin/templates/tabler/admin/model/details.html Adds Tabler model details page template.
flask_admin/templates/tabler/admin/model/create.html Adds Tabler model create page template.
flask_admin/templates/tabler/admin/master.html Tabler master template extending admin_base_template.
flask_admin/templates/tabler/admin/lib.html Adds Tabler-flavored shared macros (forms, pager, modal helpers).
flask_admin/templates/tabler/admin/layout.html Adds Tabler menu + message macros including Tabler icon rendering.
flask_admin/templates/tabler/admin/index.html Adds Tabler index template scaffold.
flask_admin/templates/tabler/admin/file/modals/form.html Adds Tabler FileAdmin modal form template.
flask_admin/templates/tabler/admin/file/list.html Adds Tabler FileAdmin list view template with actions + modals.
flask_admin/templates/tabler/admin/file/form.html Adds Tabler FileAdmin upload/mkdir/rename form template.
flask_admin/templates/tabler/admin/base.html Adds full Tabler base layout, assets, and theme toggle logic.
flask_admin/templates/tabler/admin/actions.html Adds Tabler actions dropdown/form/script macros.
flask_admin/templates/tabler/admin/_theme_toggle.html Adds dark/light toggle markup for header/sidebar.
flask_admin/static/admin/js/tabler_modal.js Adds JS to load modal content via fetch when opened.
flask_admin/static/admin/js/tabler_filters.js Adds JS to manage filter UI for Tabler model list.
flask_admin/static/admin/css/tabler/rediscli.css Adds Tabler Redis CLI styling.
flask_admin/static/admin/css/tabler/admin.css Adds Tabler admin UI styling helpers.
flask_admin/model/typefmt.py Extends bool formatter output to include Tabler icon classes + color classes.
flask_admin/model/base.py Documents new ICON_TYPE_TABLER option for model menu icons.
flask_admin/consts.py Adds ICON_TYPE_TABLER = "ti".
flask_admin/base.py Documents new ICON_TYPE_TABLER option for menu icons.
examples/tabler/static/js/active-layout.js Adds example-only JS to highlight active theme options in menu.
examples/tabler/pyproject.toml Adds example project metadata/deps for Tabler demo app.
examples/tabler/main.py Adds runnable Tabler example app demonstrating layouts/theme switching.
examples/tabler/data.py Adds sample DB generator for the Tabler example.
examples/tabler/README.md Adds docs for running and using the Tabler example.
examples/tabler/.python-version Pins Python version for the example.
doc/changelog.rst Notes experimental TablerTheme in changelog.
Files not reviewed (1)
  • flask_admin/static/tabler/css/tabler-themes.min.css: Language not supported
Comments suppressed due to low confidence (5)

flask_admin/templates/tabler/admin/model/details.html:1

  • This template references admin_static.url(...) but never imports admin/static.html as admin_static (unlike other Tabler templates). This will raise a Jinja UndefinedError at render time. Add {% import 'admin/static.html' as admin_static with context %} near the top of the file.
    flask_admin/templates/tabler/admin/model/details.html:1
  • This template references admin_static.url(...) but never imports admin/static.html as admin_static (unlike other Tabler templates). This will raise a Jinja UndefinedError at render time. Add {% import 'admin/static.html' as admin_static with context %} near the top of the file.
    flask_admin/templates/tabler/admin/model/row_actions.html:1
  • This embeds translated text inside a single-quoted JS string in an onclick attribute. If the translation contains an apostrophe (or other characters that become ' after HTML entity decoding), it can break the JS string and potentially enable injection. Prefer passing a JSON-encoded string, e.g. faHelpers.safeConfirm({{ _gettext('...')|tojson }});, or move the handler into JS and read text from a data-* attribute encoded with |tojson.
    flask_admin/tests/tabler/test_renders.py:1
  • TablerTheme.__post_init__ now validates layout and raises ValueError for unsupported values, but this new behavior isn’t exercised here. Add a small test asserting invalid layouts raise (e.g., TablerTheme(layout='bad')) so the validation and error message remain stable.
    flask_admin/templates/tabler/admin/rediscli/console.html:1
  • <input> is a void element in HTML and should not be closed with </input>. Use <input type=\"text\"> (or <input type=\"text\" />) to avoid invalid markup and improve compatibility with strict HTML tooling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +64 to +73
{% if _t.use_cdn %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler-themes.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tabler-icons/3.35.0/tabler-icons.min.css" />
{% else %}
<link href="{{ url_for('admin.static', filename='tabler/css/tabler.min.css', v='1.4.0') }}" rel="stylesheet" {{ admin_csp_nonce_attribute }}>
<link href="{{ url_for('admin.static', filename='tabler/css/tabler-themes.min.css', v='1.4.0') }}" rel="stylesheet" {{ admin_csp_nonce_attribute }}>
<link href="{{ url_for('admin.static', filename='tabler/icons/tabler-icons.min.css', v='3.40.0') }}" rel="stylesheet" {{ admin_csp_nonce_attribute }}>
{% endif %}
<link href="{{ url_for('admin.static', filename='admin/css/tabler/admin.css') }}" rel="stylesheet" {{ admin_csp_nonce_attribute }}>
Comment thread examples/tabler/data.py
db.drop_all()
db.create_all()

first_names = [
Comment thread examples/tabler/data.py
Comment on lines +35 to +36
]
last_names = [
Comment thread examples/tabler/data.py
Comment on lines +62 to +67
]

for i in range(len(first_names)):
user = User()
user.name = first_names[i] + " " + last_names[i]
user.email = first_names[i].lower() + "@example.com"
{% endif %}
<button type="submit" class="btn btn-sm btn-ghost-danger"
title="{{ _gettext('Delete Directory') }}"
onclick="return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\' recursively?', name=name) }}')">
{% endif %}
<button type="submit" class="btn btn-sm btn-ghost-danger"
title="{{ _gettext('Delete File') }}"
onclick="return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\'?', name=name) }}')">
Comment on lines +11 to +13
fetch(href)
.then(function (response) { return response.text(); })
.then(function (html) { modalContent.innerHTML = html; });
function getCount(name) {
const idx = name.indexOf('_');
if (idx === -1) return 0;
return parseInt(name.substr(3, idx - 3), 10);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

Template Mode w/ Tabler

4 participants