Embed a Cooking Chart
One script and one tag. The recipe is drawn in the reader's browser, in the language it is written in, with no cookies and no tracking. It works on any website, static blog or CMS that allows HTML.
In one line
Load the script once and write the recipe in CCL inside <cooking-chart>. Indentation doesn't matter.
<script src="https://cookingchart.org/embed.js" defer></script>
<cooking-chart>
recipe "Tomato toast"
ingredients
bread 1 slice
tomato 1 pc
oil 1 tbsp
toast = toast(bread) 3min unattended
grated = grate(tomato) 2min
dish = assemble(toast, grated, oil) 1min
serve dish
</cooking-chart>
Result
From a .ccl file
If your recipes live in files, point to them with src. With controls readers can switch between as soon as possible and just in time.
Files on your own site always work; files on another domain need that server to allow CORS.
<cooking-chart src="/recipes/country_bread.ccl" controls></cooking-chart>
Result
Chart, work plan and method
Besides the chart, the element can show the recipe as text: the method, with sentences in the order you cook, and the work plan, step by step with its minutes. Pick which ones with view; if there are several, they show as tabs in that order.
With schema, the recipe is also published as schema.org structured data, which search engines use to show recipes.
<cooking-chart src="/recipes/chicken_tacos.ccl" view="method chart plan" schema></cooking-chart>
Result
In Markdown
If your blog is static (Hugo, Astro, Jekyll, Eleventy…), you don't need HTML: any code block marked as ccl turns into a chart on its own.
<!-- Once, in your blog template --> <script src="https://cookingchart.org/embed.js" defer></script> <!-- In any .md post --> ```ccl recipe "Soft-boiled egg" ingredients egg 1 pc water 500 ml boiling_water = boil(water) 5min soft_egg = boil(egg, boiling_water) 6min no wait serve soft_egg ```
Options
Attributes of <cooking-chart>. All of them are optional.
| Attribute | Values | What it does |
|---|---|---|
| src | URL | Loads the recipe from a .ccl file instead of the tag's text. |
| mode | asap · jit | As soon as possible or just in time. Defaults to the recipe's declared schedule. |
| lang | en · es | Forces the language of the chart labels. Defaults to the recipe's language. |
| controls | — | Shows the schedule switch. |
| variant | signature | Thumbnail without text: the recipe's visual signature. |
| view | chart · method · plan · all | What to show: chart, method or work plan. Several, separated by spaces, show as tabs. Default: chart. |
| schema | — | Publishes the recipe on the page as schema.org/Recipe JSON-LD. |
With an iframe
For platforms that don't allow scripts but do allow iframes. The editor generates the code with the Embed button. The recipe travels in the URL hash (whatever comes after #), which browsers never send to the server.
<iframe src="https://cookingchart.org/e#ccl=cmVjaXBlICJ4Ii…"
width="100%" height="420" style="border:0"
loading="lazy" title="Lasagna bolognese"></iframe>
As an image
If a platform allows neither scripts nor iframes (social networks, some newsletter tools), use Download SVG in the editor. You lose interactivity, but the drawing is identical and scales without losing quality.
JavaScript
The script exposes window.CookingChart, and each <cooking-chart> has a small API.
<cooking-chart id="my-recipe"></cooking-chart>
<script>
const el = document.getElementById('my-recipe');
// Change the recipe: it is redrawn
el.source = recipeText;
// Know when it was drawn (and whether there were errors)
el.addEventListener('cooking-chart:render', e => {
const { recipe, diagnostics, ok } = e.detail;
});
// Validate without drawing
const { ok, diagnostics } = CookingChart.parse(text, { locale: 'en' });
</script>
| API | What it does |
|---|---|
| el.source = text | Changes the element's recipe and redraws it. |
| cooking-chart:render | Event after each render; detail has the recipe, the diagnostics and whether it is valid. |
| CookingChart.parse(text, options) | Parses a recipe without drawing it. { locale: "en" } for English messages. |
| CookingChart.prepare(recipe, mode) | Schedules a parsed recipe: times, slack, lanes. |
| CookingChart.method(scheduled) | The method as data: ingredients, equipment and sentences per moment. |
| CookingChart.schema(scheduled) | The schema.org/Recipe object, to publish it your own way. |
| CookingChart.upgrade(root) | Converts ccl blocks added after the page loaded. |
| CookingChart.registerPack(pack) | Registers a vocabulary pack (see below). |
Styling
The chart lives inside a Shadow DOM: your site's styles don't break it and its styles don't touch your site. It takes the width of its container; to limit it, style the tag. The inner card is exposed as ::part(card).
Step colors are part of the standard and can't be changed, so any reader recognizes a Cooking Chart on any website.
cooking-chart { max-width: 960px; margin: 2rem auto; }
/* The inner card can be tweaked from outside */
cooking-chart::part(card) { border-radius: 0; border-color: #ddd; }
Your own vocabulary
If your recipes use verbs outside the base vocabulary (a kitchen-robot brand, pastry techniques…), register a pack and enable it in the recipe with use "name". For a one-off, a verb with the x- prefix and @kind: is enough.
<script src="https://cookingchart.org/embed.js"></script>
<script>
CookingChart.registerPack({
id: 'thermomix', version: '0.1',
names: { es: 'thermomix', en: 'thermomix' },
verbs: [
// [id, kind, attention, Spanish forms, English forms]
['varoma', 'heat', 'passive', 'cocer_al_vapor', 'steam'],
['turbo', 'prep', 'active', 'turbo', 'turbo']
]
});
</script>
<!-- and in the recipe: use "thermomix" -->
Privacy
The script uses no cookies, doesn't track and doesn't send your recipes anywhere: all parsing and drawing happens in the reader's browser. It only downloads the drawing library (d3) from a public CDN if your page doesn't already have it.