Skip to content

Let hosts register named table styles #1289

Open

Description

@fillipeppalhares

The problem

Every table in Lexxy looks the same, and there's no way to tell tables apart. That matters as soon as an app needs two kinds of table in the same content. My use-case is a borderless table (acting as columns) in a blog page that can also have normal tables.

Overriding the table CSS in the host app only works if every table should change. The editor doesn't know which table is which, so nothing in the saved content can distinguish them.

The idea

Let the host register named table styles in the Lexxy config. Each table carries at most one style, chosen from a small menu in the table tools.

Lexxy.configure({
  default: {
    tables: {
      styles: [
        { name: "borderless", label: "No borders" },
        { name: "compact", label: "Compact" }
      ]
    }
  }
})
  • The chosen name is saved in the editor state JSON as tableStyle on the table node.
  • In HTML, the name is saved as a lexxy-content__table--<name> class on the <table>. It's the only channel that survives the current sanitizers without loosening them: DOMPurify restricts style to color and background-color, and Rails' sanitizer keeps class on <table>.
  • Names that aren't registered are dropped on load and paste, so arbitrary classes can't come in through content.
  • A table has at most one style. Each style is meant to be a complete, self-contained look, not a building block to combine.

Why this shape

Lexxy decides nothing about how a style looks. It ships no CSS for these names, so the feature isn't tied to "borderless" or to any other appearance:

.lexxy-content__table--borderless th,
.lexxy-content__table--borderless td {
  border-color: transparent;
}

entries in a list plus some CSS. The editor only provides the part an app can't build itself: telling tables apart in the editor, in the JSON state, and in the saved HTML.

It's also opt-in. With no styles configured there's no menu and no change in behavior, and existing content and JSON are unchanged.

What it looks like

Image Image

Implementation

A working implementation is on my fork, as one commit: compare view.

  • WrappedTableNode stores the style and round-trips it through JSON and HTML.
  • TablesExtension adds a setTableStyle command and a transform that drops unregistered names.
  • A "Style" menu in the table tools, shown only when styles are configured.
  • Config names are validated up front: lowercase letters, digits and dashes, and unique. selection is reserved because lexxy-content__table--selection already exists.
  • Playwright tests (set, replace, clear, undo, export and re-import, JSON, unregistered names, no menu by default), a Vitest test for the config validation, and a Capybara test for the save, render and re-edit round trip.
  • Docs in configuration.md.

Questions

  1. Is this something you'd want in Lexxy? I'm happy to open a PR, and to split it up if that would make review easier.
  2. Is tables.styles the right place for the option? Add configurable tables support #1104 makes tables a boolean (tables: false). If that merges first, this would become something like tables: { enabled, styles }, or I could use a separate top-level key. I'm fine either way.
  3. Are you happy with the lexxy-content__table--<name> class convention for the saved HTML?

Activity

  1. fillipeppalhares commented on Oct 26, 2026

    @fillipeppalhares
    ContributorAuthor

    Hi there, @jorgemanrubia! Could you take a look a this, please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions