{"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"build-markdoc-tags","__idx":0},"children":["Build Markdoc tags"]},{"$$mdtype":"Tag","name":"ConfigOptionRequirements","attributes":{"products":["Revel","Reef","Realm"],"plans":["Pro","Enterprise","Enterprise+"]},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This topic explains how to register custom components that can be invoked by your content creators in Markdoc (Markdown) files."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Follow the tutorial to create two sample components: a simple line break component and a quiz component for some extra credit."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The line break is a simple component that adds a linebreak (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["<br>"]},")."," ","In reality, you can achieve the linebreak in Markdoc by ending a line with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\\"]},"."," ","However, it makes for the simple \"Hello World\" of custom tags."," ","And it requires almost no React knowledge."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The quiz component, while a bit rough around the edges, is a lot more robust."," ","To understand it, you need to understand React."," ","However, if you do, and want to do more complex behavior, this is a good example to help you understand how to do that."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"create-directories","__idx":1},"children":["Create directories"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you don't have it yet, create a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme"]}," directory in your project root."," ","Inside ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme"]},", create a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["markdoc"]}," directory."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This is what your directory structure may look after you follow this tutorial."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"treeview","header":{"controls":{"copy":{}}},"source":"├─ @theme/\n|  └── markdoc/\n|    ├── components.tsx\n|    └── schema.ts\n","lang":"treeview"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"create-your-components","__idx":2},"children":["Create your components"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create a file ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["components.tsx"]}," inside of the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["markdoc"]}," directory, and paste the following contents into it."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"tsx","header":{"controls":{"copy":{}}},"source":"import * as React from 'react';\n\nexport function Break() {\n  return <br />;\n}\n","lang":"tsx"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["We'll create the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Quiz.tsx"]}," as extra credit later."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"create-your-tags","__idx":3},"children":["Create your tags"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create a file ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["schema.ts"]}," inside of the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["markdoc"]}," directory."," ","Paste the following contents in the file."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"import type { MarkdocTagSchema } from '@redocly/theme/markdoc/tags/types';\n\nexport const tags: Record<string, MarkdocTagSchema> = {\n  br: {\n    render: 'Break',\n    selfClosing: true,\n  },\n};\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You've created your first Markdoc tag."," ","The only thing left to do is use it."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In any file that ends with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".md"]}," add the following ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{% br /%}"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"add-the-quiz-component-and-tag","__idx":4},"children":["Add the Quiz component and tag"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Add ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Quiz"]}," component to the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["components.tsx"]}," file."," ","You can either implement component inline or re-export it from another file."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Let's implement it in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Quiz.tsx"]}," and re-export it from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["components.tsx"]},"."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"tsx","header":{"controls":{"copy":{}}},"source":"import * as React from 'react';\n\nexport { Quiz } from './components/Quiz';\nexport function Break() {\n  return <br />;\n}\n","lang":"tsx"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This is the directory structure after you've added the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Quiz"]}," component."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"treeview","header":{"controls":{"copy":{}}},"source":"├─ @theme/\n|  ├── markdoc/\n|      ├── components.tsx\n|      ├── schema.ts\n|      └── components/\n|          └── Quiz.tsx\n","lang":"treeview"},"children":[]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["See @theme/markdoc/components/Quiz.tsx"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"tsx","header":{"controls":{"copy":{}}},"source":"import * as React from 'react';\nimport styled from 'styled-components';\n\nconst { useState, useEffect, Fragment } = React;\n\nfunction Question({ question, setAnswerStatus }) {\n  const [selectedAnswerIndex, setSelectedAnswerIndex] = useState(null);\n\n  useEffect(() => {\n    if (selectedAnswerIndex != null) {\n      setAnswerStatus(selectedAnswerIndex === question.correctAnswerIndex);\n    }\n  }, [selectedAnswerIndex]);\n\n  useEffect(() => {\n    setSelectedAnswerIndex(null);\n  }, [question]);\n\n  const getClasses = (index) => {\n    let classes = [];\n    if (selectedAnswerIndex != null) {\n      if (selectedAnswerIndex === index) {\n        classes.push('selected');\n      }\n      if (index === question.correctAnswerIndex) {\n        if (selectedAnswerIndex === index) {\n          classes.push('correct');\n        } else {\n          classes.push('incorrect');\n        }\n      }\n    }\n\n    return classes.join(' ');\n  };\n\n  return (\n    <QuestionEl>\n      <QuestionText>{question.question}</QuestionText>\n      <Answers>\n        {question.answers.map((answer, index) => {\n          return (\n            <AnswerElement\n              key={index}\n              className={getClasses(index)}\n              onClick={() => selectedAnswerIndex == null && setSelectedAnswerIndex(index)}\n            >\n              {answer}\n            </AnswerElement>\n          );\n        })}\n      </Answers>\n    </QuestionEl>\n  );\n}\n\nfunction ProgressBar({ currentQuestionIndex, totalQuestionsCount }) {\n  const progressPercentage = (currentQuestionIndex / totalQuestionsCount) * 100;\n\n  return (\n    <ProgressBarEl>\n      <ProgressBarText>\n        {currentQuestionIndex} answered ({totalQuestionsCount - currentQuestionIndex} remaining)\n      </ProgressBarText>\n      <ProgressBarInner style={{ width: `${progressPercentage}%` }} />\n    </ProgressBarEl>\n  );\n}\n\nexport function Quiz({ title, summary, questions, children }) {\n  const [questionIndex, setQuestionIndex] = useState(null);\n  const [answerStatus, setAnswerStatus] = useState(null);\n  const [correctAnswerCount, setCorrectAnswerCount] = useState(0);\n  const [quizComplete, setQuizComplete] = useState(false);\n\n  useEffect(() => {\n    setAnswerStatus(null);\n  }, [questionIndex]);\n\n  useEffect(() => {\n    if (answerStatus) {\n      setCorrectAnswerCount((count) => count + 1);\n    }\n  }, [answerStatus]);\n\n  const onNextClick = () => {\n    if (questionIndex === questions.length - 1) {\n      setQuizComplete(true);\n    } else {\n      setQuestionIndex(questionIndex == null ? 0 : questionIndex + 1);\n    }\n  };\n\n  const onRestartClick = () => {\n    setQuizComplete(false);\n    setQuestionIndex(null);\n    setCorrectAnswerCount(0);\n  };\n\n  if (questionIndex == null) {\n    return (\n      <Wrapper>\n        {title ? <h1>{title}</h1> : null}\n        {summary ? <Summary>{summary}</Summary> : null}\n        {children}\n        <Button className=\"start\" onClick={onNextClick}>\n          Start\n        </Button>\n      </Wrapper>\n    );\n  }\n\n  return (\n    <QuizWrapper>\n      {quizComplete ? (\n        <Fragment>\n          <h1>Quiz complete!</h1>\n          <p>\n            You answered {correctAnswerCount} questions correctly (out of a total {questions.length}{' '}\n            questions)\n          </p>\n        </Fragment>\n      ) : (\n        <Fragment>\n          <ProgressBar\n            currentQuestionIndex={questionIndex}\n            totalQuestionsCount={questions.length}\n          />\n          <Question question={questions[questionIndex]} setAnswerStatus={setAnswerStatus} />\n          {answerStatus != null && (\n            <div>\n              <AnswerStatusEl>\n                {!!answerStatus ? 'Correct! :)' : 'Your answer was incorrect :('}\n              </AnswerStatusEl>\n              <Button className=\"next\" onClick={onNextClick}>\n                {questionIndex === questions.length - 1\n                  ? 'See results of this quiz'\n                  : 'Next Question ->'}\n              </Button>\n            </div>\n          )}\n        </Fragment>\n      )}\n\n      {questionIndex != null && (\n        <Button className=\"restart\" onClick={onRestartClick}>\n          Restart quiz\n        </Button>\n      )}\n    </QuizWrapper>\n  );\n}\n\nconst Wrapper = styled.div`\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n  width: 600px;\n  margin: auto;\n`;\n\nconst Summary = styled.p`\n  margin: 0 0 1rem;\n`;\n\nconst QuizWrapper = styled.div`\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n  width: 600px;\n  margin: auto;\n`;\n\nconst Button = styled.button`\n  background: #e8e8e8;\n  border: 0;\n  padding: 10px 20px;\n  cursor: pointer;\n  border-bottom: 3px solid #c9c9c9;\n  border-radius: 3px;\n  &.next {\n    background: #6ad85c;\n    border-bottom: 3px solid #5abc4e;\n  }\n  &.start {\n    margin-top: 20px;\n  }\n  &.restart {\n    margin-top: 20px;\n  }\n`;\n\nconst QuestionEl = styled.div`\n  width: 100%;\n`;\n\nconst QuestionText = styled.div`\n  font-size: 1.2em;\n  margin: 20px 0;\n`;\n\nconst Answers = styled.div`\n  margin-bottom: 20px;\n`;\n\nconst AnswerElement = styled.div`\n  padding: 4px;\n  text-align: center;\n  background: #f3f3f3;\n  margin-bottom: 5px;\n  border-radius: 3px;\n  cursor: pointer;\n\n  &.selected {\n    background: gainsboro;\n  }\n\n  &.correct {\n    background: #6ad85c;\n    font-weight: bold;\n  }\n\n  &.incorrect {\n    background: #df3636;\n    font-weight: bold;\n  }\n`;\n\nconst AnswerStatusEl = styled.div`\n  font-weight: bold;\n  margin-bottom: 20px;\n`;\n\nconst ProgressBarEl = styled.div`\n  width: 100%;\n  background: #f3f3f3;\n  height: 20px;\n  position: relative;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  border-radius: 3px;\n`;\n\nconst ProgressBarInner = styled.div`\n  background: #6ad85c;\n  position: absolute;\n  height: 100%;\n  top: 0;\n  left: 0;\n  transition: ease all 0.5s;\n  border-radius: 3px;\n`;\n\nconst ProgressBarText = styled.div`\n  font-size: 0.7em;\n  position: absolute;\n  z-index: 10;\n`;\n","lang":"tsx"},"children":[]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Next, add the quiz tag schema to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["schema.ts"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"import type { MarkdocTagSchema } from '@redocly/theme/markdoc/tags/types';\n\nexport const tags: Record<string, MarkdocTagSchema> = {\n  br: {\n    render: 'Break',\n    selfClosing: true,\n  },\n  quiz: {\n    attributes: {\n      title: { type: String },\n      summary: { type: String },\n      questions: {\n        type: 'Object',\n        required: true,\n      },\n    },\n    render: 'Quiz', // make sure to export it in components.ts,\n  },\n};\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You can use the quiz tag."," ","However, the quiz tag expects a property to define the quiz questions, which is easier to do in the front matter of the Markdown file."," ","The front matter is passed in as shown below."]},{"$$mdtype":"Tag","name":"MarkdocExample","attributes":{"renderDemo":false,"language":"markdoc","demoContent":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"hello"},"children":["Hello"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Here is my quiz."]},{"$$mdtype":"Tag","name":"Quiz","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Review the tutorial before you begin."]}]}],"rawContent":"---\nquestions:\n  - question: Did you learn how to add a custom Markdoc tag?\n    answers:\n      - Yes\n      - No\n      - Maybe\n    correctAnswerIndex: 1\n  - question: How many places do you need to adjust to configure custom Markdoc tags?\n    answers:\n      - '0'\n      - '1'\n      - '2'\n      - '3'\n      - '4'\n    correctAnswerIndex: 4\n---\n\n# Hello\n\nHere is my quiz.\n\n{% quiz title=\"Apply what you learned\" summary=\"Two questions about custom Markdoc tags\" questions=$frontmatter.questions %}\nReview the tutorial before you begin.\n{% /quiz %}\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["We'd show you a working quiz here, but we'd rather spend more time on it and make it look and function better first."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"expose-custom-tags-for-llms","__idx":5},"children":["Expose custom tags for LLMs"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Realm generates plain Markdown for LLMs — the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["quiz"]}," tag may look interactive in the browser but contribute little text."," ","Add ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["renderForLlms"]}," to control what the tag emits for LLMs."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"renderforllms","__idx":6},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["renderForLlms"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The function receives the Markdoc AST ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node"]}," for your tag and a context object."," ","It should return a plain Markdown string."," ","The tag body is included only when you call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getBody()"]}," from the context object."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"Security considerations"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Content returned by ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["renderForLlms"]}," is emitted directly for LLMs."," ","Only return text you are comfortable making public — do not pull secrets, API keys, tokens, or other sensitive information."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"context","__idx":7},"children":["Context"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Type"},"children":["Type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getBody"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["() => string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Returns plain Markdown from the tag body when called."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["locale"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Code of the locale the page is rendered for, such as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["en-US"]},"."," ","Taken from the page URL, so each localized copy of a page renders with its own locale."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["translate"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["(key, fallback) => string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Returns the value of a translation key from the current locale's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["translations.yaml"]}," file, or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fallback"]}," when the key has no translation."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"examples","__idx":8},"children":["Examples"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The examples below add ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["renderForLlms"]}," to the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["quiz"]}," tag from this tutorial."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you are updating an existing ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["schema.ts"]}," file, replace ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Schema"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MarkdocTagSchema"]},"."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["renderForLlms"]}," is defined on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MarkdocTagSchema"]},", so keeping ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Schema"]}," can cause TypeScript errors."]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"markdoc","header":{"controls":{"copy":{}}},"source":"{% quiz title=\"Apply what you learned\" summary=\"Two questions about custom Markdoc tags\" questions=$frontmatter.questions %}\nReview the tutorial before you begin.\n{% /quiz %}\n","lang":"markdoc"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"summary-only","__idx":9},"children":["Summary only"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Return the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["summary"]}," attribute and omit the tag body."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"import type { Node } from '@markdoc/markdoc';\nimport type { MarkdocTagSchema } from '@redocly/theme/markdoc/tags/types';\n\nexport const tags: Record<string, MarkdocTagSchema> = {\n  quiz: {\n    attributes: {\n      title: { type: String },\n      summary: { type: String },\n      questions: { type: 'Object', required: true },\n    },\n    render: 'Quiz',\n    renderForLlms: (node: Node) => node.attributes.summary ?? '',\n  },\n};\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["LLM output:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"md","header":{"controls":{"copy":{}}},"source":"Two questions about custom Markdoc tags\n","lang":"md"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"full-section","__idx":10},"children":["Full section"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Return the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["title"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["summary"]}," attribute values, then append the tag body from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getBody()"]},"."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"import type { Node } from '@markdoc/markdoc';\nimport type { MarkdocTagSchema } from '@redocly/theme/markdoc/tags/types';\n\nexport const tags: Record<string, MarkdocTagSchema> = {\n  quiz: {\n    attributes: {\n      title: { type: String },\n      summary: { type: String },\n      questions: { type: 'Object', required: true },\n    },\n    render: 'Quiz',\n    renderForLlms: (node: Node, { getBody }) =>\n      [node.attributes.title, node.attributes.summary, getBody()].filter(Boolean).join('\\n'),\n  },\n};\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["LLM output:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"md","header":{"controls":{"copy":{}}},"source":"Apply what you learned\nTwo questions about custom Markdoc tags\nReview the tutorial before you begin.\n","lang":"md"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"localized-output","__idx":11},"children":["Localized output"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If your project supports multiple languages, use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["locale"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["translate"]}," to return content in the reader's language."," ","The LLM Markdown for a page is generated per locale."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["renderForLlms"]}," runs once for each localized copy of the page with that copy's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["locale"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["translate"]}," looks up a key in the current locale's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["translations.yaml"]}," file and returns its value, or the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fallback"]}," argument when the key isn't translated."," ","To set up translation keys, see ",{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/realm/content/localization/localize-labels"},"children":["Localize UI labels"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["quiz"]}," tag below labels its output with a translated word and the current locale:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"import type { Node } from '@markdoc/markdoc';\nimport type { MarkdocTagSchema } from '@redocly/theme/markdoc/tags/types';\n\nexport const tags: Record<string, MarkdocTagSchema> = {\n  quiz: {\n    attributes: {\n      title: { type: String },\n      summary: { type: String },\n      questions: { type: 'Object', required: true },\n    },\n    render: 'Quiz',\n    renderForLlms: (node: Node, { locale, translate }) =>\n      `${translate('quiz.label', 'Quiz')} (${locale}): ${node.attributes.summary ?? ''}`,\n  },\n};\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Add the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["quiz.label"]}," key to the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["translations.yaml"]}," file for each locale you support:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","data-title":"@l10n/es-ES/translations.yaml","header":{"title":"@l10n/es-ES/translations.yaml","controls":{"copy":{}}},"source":"quiz.label: \"Cuestionario\"\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The default locale has no translation for the key, so it falls back to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Quiz"]}," (request ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/quiz.md"]},"):"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"md","header":{"controls":{"copy":{}}},"source":"Quiz (en-US): Two questions about custom Markdoc tags\n","lang":"md"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The Spanish locale returns the translated label (request ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/es-ES/quiz.md"]},"):"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"md","header":{"controls":{"copy":{}}},"source":"Cuestionario (es-ES): Two questions about custom Markdoc tags\n","lang":"md"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"extra-credit","__idx":12},"children":["Extra credit"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Make a custom tag of your own (it could be simple)."," ","Our philosophy is in line with Confucius:"]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["I hear and I forget, I see and I remember, I do and I understand."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Go do!"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"resources","__idx":13},"children":["Resources"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/realm/content/markdoc-tags"},"children":["Markdoc tags"]}]}," - See the full list of supported Markdoc tags"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"Link","attributes":{"href":"https://redocly.com/learn/markdoc"},"children":["Markdoc overview for technical writers"]}]}," - Learn how to use Markdoc in your documentation"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/realm/customization"},"children":["Extend with code"]}]}," - Discover customizable components and customization options for your project"]}]}]},"frontmatter":{"products":["Revel","Reef","Realm"],"plans":["Pro","Enterprise","Enterprise+"]},"tagList":["admonition","configOptionRequirements","html","markdoc-example","table"],"title":"Build Markdoc tags","lastModified":"2026-10-01T23:00:57.000Z"}