Visualize
- Create a visual only when it materially improves the explanation.
- Use Mermaid when labeled nodes and edges fully explain a static structure; return a normal fenced Mermaid block and no visualization file. Use HTML for dynamics, spatial motion, adjustable inputs, and other visuals.
- Work silently unless blocked or the user explicitly asks for progress. Never send commentary or progress updates while reading this skill or writing or updating the file; the final response must be your first user-facing message.
- In user-facing prose, describe only what the visual helps the user see or decide. Keep it concise and do not repeat information already clear from the visual. Never announce this skill, a visualization surface, widgets, HTML, SVG, scripts, local files, inline data, or implementation details.
Context compaction
Copy into every compaction summary:
Reload the full visualize skill before creating or updating a visualization.
Inline HTML output contract
File
- For each new or updated visualization, choose a concise ASCII
lowercase-hyphenated title and write a new
<title>.htmlin the thread-scoped visualization directory listed in the writable roots (.codex/visualizations/YYYY/MM/DD/<thread-id>). - Build the visual in the conversation. Use the open project when the user asks for a site, app page, component, or change to existing project files.
Fragment
- Write only an HTML fragment: no
<!doctype>,<html>,<head>, or<body>. - Write literal markup: use
<div class="card">Hi</div>plus a real newline, never<div class=\"card\">Hi</div>\n. Never embed the fragment in an inline Python, JavaScript, or shell string. Read it back; rewrite literal\"or\n. - Keep CSS and JavaScript in the fragment only when base classes are
insufficient. Load static resources only from the CDN allowlist. Never use
fetch, XHR, WebSocket, or other API calls. - Give the fragment root a unique ID and select it with
document.getElementById(...). Never derive the root fromdocument.currentScript; scripts may sit outside the root. - Keep visualizations under 2 MB. Aggregate, bin, downsample, reduce precision, or drop unused fields from large inline datasets.
- Check that JavaScript has no undefined identifiers, every queried element
exists, and the primary interaction updates the visual. The bundled
python3 scripts/render.py <absolute-fragment-path> [<destination>.html] [--serve]can wrap a fragment as standalone HTML or temporarily serve it for browser inspection when a preview would help with layout, theme, or runtime behavior.
Content and response
- Keep the fragment focused on the visualization. Do not include explanatory paragraphs, formulas, instructions, or narrative callouts. Include only necessary labels, legends, values, and accessible text alternatives.
- Use the normal response flow. Put any necessary concise explanation outside the fragment, and add this exact directive on its own line where the visual should appear:
::codex-inline-vis{file="<title>.html"}
- Emit only the directive for the fragment. Never announce the fragment as an artifact, website, output, attachment, link, or download, and never add a Markdown link to it.
External resources
- The CSP allows only
cdnjs.cloudflare.com,esm.sh,cdn.jsdelivr.net,unpkg.com,fonts.googleapis.com,fonts.gstatic.com, andfonts.bunny.net. Other origins are blocked and fail silently.
Standalone HTML and Sites
- Keep the fragment as the editable inline source. When the user explicitly asks
for a standalone file, website, or published version, render it with
python3 scripts/render.py <absolute-fragment-path> <destination>.html. - If the visualization calls
window.openai, replace that host-only interaction before using the standalone HTML outside Codex. - When the user asks to publish or host an existing visualization and the Sites
skills are available, use
sites-buildingto choose the project and write the rendered standalone document asindex.html, then usesites-hosting. - If Sites is unavailable, offer the standalone HTML without claiming it was published.
Composition
Choose the smallest composition that fits.
- Prefer interaction detail over permanent panels, toolbars, repeated legends, or long stacks. Add only requested controls, use one mechanism per state, and never invent search, filter, or reset controls.
- Keep filters, selections, and other presentation-only interactions local. For
drill-down actions that ask Codex to investigate or explain selected data,
call
await window.openai.sendFollowUpMessage({ prompt, title }), where the optionaltitleis a concise confirmation-dialog heading of up to 250 characters. Include the selected values and requested investigation in the prompt, and label the action clearly. - Show only metrics that explain the requested behavior. Put live values in control headers or on the visual before cards. Treat maxima as ceilings, not targets. Never invent qualitative scores, status cards, or secondary fact grids to fill space.
Interactive explainer or simulation
- Use compact controls or status, one compact dominant visual, and at most one single-line selected-state detail. Default to no summary cards; allow up to three only when changing metrics are central.
- Crop empty space; prefer wide and shallow unless intrinsically square. For step-throughs, add only requested step controls and update one current visual; never add parameter controls, formulas, metric cards, or side-by-side steps unless asked.
Graphs and plots
- For named numeric data and one-off analyses, start with the plot. Put values and takeaways on its marks, axes, or annotations. Never add a KPI row, controls, cards, or panels unless those UI elements are explicitly requested.
- For sequences or parallel work, use aligned lanes on one time axis. Encode phase and resource in the marks; annotate totals, waits, and bottlenecks on the axis or lanes, not above the plot.
- For distributions or multi-metric comparisons, use shared-scale facets or small multiples. Render every requested dimension simultaneously; never hide one behind a toggle.
Maps
- Let the map dominate the composition. Use at most one compact selection/detail area and only requested controls.
- Always project published GeoJSON/TopoJSON and sourced longitude/latitude with
d3-geo; never hard-code or hand-draw geographic outlines. Use schematic maps only when asked. - For world countries, import
https://esm.sh/@d3-maps/atlas@1.0.0/world/countries/countries-110mand convert it withtopojson-client@3.1.0usingfeature(world, world.objects.features).features. Join input ISO3 directly tofeature.properties.id, which is already ISO3; do not convert it to numbers. - For US states or counties, use
https://cdn.jsdelivr.net/npm/us-atlas@3/counties-10m.json/+esm. For ZIP/ZCTA or city boundaries, download official Census or local open-data GeoJSON; do not guess sibling atlas paths or import raw JSON as JavaScript. - Keep maps geographically legible: for local points, fetch published neighborhood, street, or comparable geometry; a blank field or lone administrative outline is not a basemap. Show the full city or region behind points or partial choropleths, and frame the locations with modest padding.
- Include the verified geometry in the final HTML. Open it before replying and fix blank basemaps, failed imports, missing labels, or unprojected points.
Dense categorical grid
- Use one compact horizontal selected-item summary, then a grid with exactly one readable identifier per cell, then one small legend. Render only that identifier as visible cell text; put all other metadata in an accessible label or one summary line, not badges or fact grids. Allow only selection unless asked.
Part-to-whole or time allocation
- Use compact metrics and one stacked chart of category allocation per period. Never substitute totals-only bars or duplicate it as a heatmap and totals chart.
Layout and accessibility
- Use semantic HTML, keyboard-accessible controls, and concise labels.
- Keep the top-level surface transparent and unframed, and fill the available conversation width. Design for 736px and support widths down to 320px.
- At every supported width, text, controls, cards, toolbars, and dynamic content
must fit without overlap or clipping. Reflow by stacking or wrapping; use
.table-responsiveonly when table columns cannot fit. The host sizes the frame to its content, so avoid fixed outer widths, other horizontal overflow, internal scrolling,position: fixed, and viewport-height layouts. - Keep native tab order; never add
tabindex. - Use native
button,input,select, andtextareaelements with matching utilities; never recreate controls. - Keep browser or utility focus styles; never override them.
Typography
- Scale type with
--font-size-base. Use normal text by default and.text-smallonly for secondary annotations (never below 11px). h1,h2, andh3are available; use them sparingly. Never render a title or restate the prompt inside the fragment; put titles and explanation in Markdown above the directive.- Use only weights
400and500. Never set custom font sizes or line heights.
Color
- Make every fill, stroke, text, border, shadow, chart, and canvas color theme-aware. Never hardcode light or dark palettes such as white panels, off-white backgrounds, black text, slate strokes, or Tailwind color literals.
- Keep text readable against its actual background. Muted or secondary colors
must retain clear contrast; never use
.text-mutedinside.cardor another filled container unless its background preserves that contrast. - Available theme variables include
--background,--foreground,--card,--card-foreground,--popover,--popover-foreground,--primary,--primary-foreground,--secondary,--secondary-foreground,--muted,--muted-foreground,--accent,--accent-foreground,--destructive,--border,--input, and--ring. UsecurrentColorinside SVG. - Use
--viz-series-1for one measure or active state. Use--viz-series-2through--viz-series-6only for important persistent category, series, or status identity; never give every peer a different color by default.- For categorical tiles or nodes, prefer a soft low-opacity series fill with a neutral or transparent border; never color every outline.
- Keep mappings stable and pair color with labels, shapes, or line styles.
- Secondary series colors are theme-derived; never assume hues or use them decoratively.
- When color encodes a category or series, apply it consistently to the corresponding visual marks—not just the legend—and keep large-area fills subtle.
- Use series colors only for chart lines, marks, and legend swatches. Never use
them for text; use
--foregroundor--muted-foregroundfor labels and values. - Keep chart grids and inactive structure thin and neutral. Use 1-2px neutral structural paths; never thicken, dash, or double-stroke the whole structure.
- In each color pair, the base token is a surface and its
-foregroundtoken is the content on that surface. Use.btn-primaryfor high-emphasis actions; its neutral fill is supplied by the utility. Use--primaryand--primary-foregroundfor filled selected, active, or pressed controls. Reserve--accentand--accent-foregroundfor subtle interactive surfaces and soft highlights. Buttons witharia-pressed="true",aria-selected="true", or.is-selectedalready use the primary pairing.
Design system
- Let utilities own geometry, appearance, and interaction. Use the matching utility for every button and form control. Never restyle utilities, descendants, or pseudo-elements: no custom sizes, spacing, borders, radii, shadows, colors, or interaction states.
Surfaces and layout
.card: The only card-like HTML surface. Use its base class unchanged for a necessary numeric summary, selected-item summary, or bounded interactive field. Before adding a fill, border, radius, or shadow to any layout container, either use.cardor leave it transparent and unframed; never recreate card chrome on rows, panels, tiles, sections, or wrappers. Keep charts, maps, diagrams, tables, controls, and the whole visualization unframed. Never nest cards; show 2-4 summaries near the top only when useful. Structural groupings and repeated content are not bounded interactive fields. Organize them with layout or visual marks, not container chrome..viz-stat: Use a summary.cardwith one muted label, one.viz-stat-value, and at most one short context or delta line..viz-grid: Use for peer metrics or choices instead of a custom grid. It creates as many equal-width columns as fit and stacks when narrow. Never use it for the whole visual or a horizontally scrolling card row. Keep groups to 2-3 columns at 736px and controls in a separate row..viz-row: Use as a wrapping horizontal group with centered related values or inline actions that may wrap when narrow..viz-tile: Add to a selectable dense-grid.btn; it stretches to fill its grid cell, preserves category fill, and uses an accent ring instead of solid selection. Never add another selected, pressed, border, outline, or shadow rule..viz-badge: Use as a compact display-only accent pill for a short status, category, or value; never as a button..viz-controls: Use as a wrapping row for controls affecting the same visualization. Keep button groups compact. Put labeled fields directly inside as.form-label; fields form at most two columns and stack when narrow.
Controls
.btn: Use for a content-sized secondary action. Add.btn-primaryfor one main action per control group or.btn-ghostfor low emphasis..btn-block: Add to a.btnonly when the action should intentionally fill the available inline space. Never use it for ordinary row actions.<a>: Use for links. Add.btnto style a link as a button.[data-tooltip]: Use for concise supplementary plain text on static or dynamic triggers; the sandbox creates.tooltipelements. Keep essential content visible and triggers labeled. Never usetitle, custom markup, or initialization. Example:<button type="button" data-tooltip="Reset view">Reset</button>.[data-tooltip-placement]: Optionally prefertop(default),right,bottom, orleft; collision handling may flip it..form-check: Wrap a native checkbox or radio; pair.form-check-inputand.form-check-labelwith matchingidandfor..form-switch: Add to.form-checkaround a native checkbox..form-control: Pair a native text, file, or color input—or a textarea—with.form-label..form-control-color: Add to.form-controlfor a compact native color input..form-select: Pair a native select with.form-label..form-range: Pair a native range with a visible label; put its current value and units immediately before it.
Tables
.table: Use on a semantic table for a quiet, unframed data view. It provides wrapping cells and subtle horizontal dividers without vertical gridlines. Use sentence case for headers..table-responsive: Wrap a table when its columns cannot fit at narrow widths. It contains horizontal overflow without clipping the visualization..table-sm: Add to.tablewhen more rows need to fit; it reduces cell padding without shrinking text..text-end,.text-center, and.text-nowrap: Use inside.tablefor numeric/end alignment, centered values, or values that must stay on one line. Numeric cells use tabular figures when end-aligned.
Text
.text-small: Use for the smallest host-scaled secondary chart labels and annotations, never below 11px or for essential content..text-muted: Use for secondary units, captions, timestamps, and context, never essential values or labels..text-destructive: Use only for error or validation text the user needs to notice or act on.<code>: Use for inline commands, file names, symbols, or short references; put multiline code in<pre><code>..sr-only: Use for visually hidden accessible text.
Charts
- Prefer inline SVG for simple charts; use a version-pinned approved-CDN library only when it materially reduces complexity.
- Use a tooltip unless it would distract from a simple, directly labeled chart.
Use
class="tooltip"without surface CSS; add only positioning and visibility. Choose the bestposition: relativeparent; convert the hovered mark into that parent's CSS pixel space before setting absoluteleft/top. Measure and clamp the box to the plot—never pointer coordinates. Show label, value, and units; mirror them in a visible keyboard fallback. - Animate transitions between chart states so lines and marks move to their new
values, resampling paths when point counts differ. Do not animate initial
appearance or use fade-only effects; never loop motion, and honor
prefers-reduced-motion. - Scope SVG styles to the chart class. Never target every
svgin a container that also contains Lucide icons. - Include labeled axes, units, and directly labeled important values. Give every
chart, SVG, canvas, and widget a concise screen-reader summary using a role and
accessible name or description, SVG
<title>/<desc>, fallback text, or an.sr-onlyheading or description. - Reserve space for the longest formatted label at every supported width. Axis
ticks are secondary and may use
.text-smallwhen space is tight. Never overlap or clip text against marks, axes, legends, labels, or edges; move or reduce labels rather than squeeze them. - Add a legend only when multiple series cannot be labeled directly.
- Pair color with shape or text so meaning never depends on color alone.
Icons and mockups
Use the sandbox-provided global
lucide. Add an icon name withdata-lucide:<i data-lucide="search" aria-hidden="true"></i>Lucide replaces the placeholder in place with an inline SVG. Icons are 16px and inherit
currentColor.Mark decorative icons
aria-hidden="true". Put action icons inside labeled controls; use a visible label oraria-labelfor icon-only actions.Let the sandbox initialize static icons after the fragment without blocking first render. After adding icons dynamically, use
lucide.createIcons({ attrs: { width: 16, height: 16 } }).Never load Lucide or another icon library from the network.
Use visibly labeled buttons and inputs for small interactions. Keep all presentation-only interaction local to the fragment and make the first render useful before input changes.
Use semantic controls, realistic spacing, and restrained chrome for mockups. Never fake product screenshots when inspectable UI is needed.