How do I restrict the types of tags and classes allowed?

Hi, [I posted this on stackoverflow, but I guess this is a good place to ask]

I would like to restrict the type of document created by a rich-text-editor. Let’s assume color/styling is handled via CSS, and I only want the content editable document to be of the type:

<p class="r">I am red</p>
<p class="g">I am green</p>
<p class="b">I am blue</p>
<p class="b">I am <b>blue bold</b></p>

The rules are:

  1. All content are in <p> tags.
  2. Every <p> has to have a class assigned to it, which can be ‘r’, ‘g’ or ‘b’.
  3. The content inside the tag can be only styled by <b> or <i>. * No nested <p> tags.

Is it possible to initialize an editor to only allow documents of this types. Note that <p> and <b> are stand-ins… it would be ok if the solution is to use <div> / <strong>. If possible, a simple codepen example or similar would be highly appreciated.

Yes, ProseMirror can do that. This is the schema that would correspond to your description:

const validClasses = ["r", "g", "b"]

const schema = new Schema({
  nodes: {
    doc: {
      content: "paragraph+"
    paragraph: {
      attrs: {class: {default: "r"}},
      content: "text<_>*",
      parseDOM: [{tag: "p", getAttrs: dom => {
        let cls = dom.className
        if (validClasses.indexOf(cls) == -1) cls = "r"
        return {class: cls}
      toDOM(node) { return ["p", {class: node.attrs.class}, 0] }
    text: {
      toDOM(node) { return node.text }
  marks: {
    italic: {
      parseDOM: [{tag: "i"}, {tag: "em"},
                 {style: "font-style", getAttrs: value => value == "italic" && null}],
      toDOM() { return ["i"] }
    bold: {
      parseDOM: [{tag: "strong"},
                 // This works around a Google Docs misbehavior where
                 // pasted content will be inexplicably wrapped in `<b>`
                 // tags with a font-weight normal.
                 {tag: "b", getAttrs: node => != "normal" && null},
                 {style: "font-weight", getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null}],
      toDOM() { return ["b"] }

You’ll need to provide some kind of UI to let users change the color of a given paragraph.

Thank you marijn! I’ll be trying this out shortly. Schemas seem highly powerful, and to be made for exactly my kind of formatting need.