# SeoPreview

> How a page looks in Google (desktop and mobile) and as an Open Graph, X, WhatsApp and LinkedIn share card, with title and description length meters.

Source: https://docs.nasaqui.com/components/seo-preview

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/seo-preview.json
```

SeoPreview shows one page the way people will meet it: a Google result on desktop and on mobile, and the share cards that Open Graph, X, WhatsApp and LinkedIn draw from the same tags. Two length meters tell the editor when the title or description is too short or will be cut. It is controlled or uncontrolled, and can edit the title, description and image URL in place.

Platform names are plain text. No logo or brand mark is drawn, so the preview never misrepresents an official mark.

## When to use

- A page or post editor that needs a live snippet while the author types.
- An SEO audit detail view that shows what a crawled page looks like today.
- A share settings screen for a marketing page.

## When not to use

- Listing many pages with scores: use [`SeoPageList`](https://docs.nasaqui.com/components/seo-pages).
- Search performance numbers (clicks, position): use [`SearchPerformanceTable`](https://docs.nasaqui.com/components/search-performance-table).

## Import

```tsx
import { SeoPreview } from "@fadymondy/nasaq/web";
```

## Quick start

```tsx
import { useState } from "react";
import { type SeoMeta, SeoPreview } from "@fadymondy/nasaq/web";

export function PageSeo() {
  const [meta, setMeta] = useState<SeoMeta>({
    title: "A complete guide to RTL design systems",
    description: "Learn how to build interfaces that work from right to left without a second stylesheet.",
    url: "https://example.com/blog/rtl-guide",
  });
  return <SeoPreview value={meta} onValueChange={setMeta} />;
}
```

## Anatomy

```
SeoPreview            data-slot="seo-preview"   (a Card)
  fields              title, description, image URL (when editable)
  LengthMeter         data-slot="length-meter"  x2, data-status
  Tabs                Google | Open Graph | X | WhatsApp | LinkedIn
    Google preview    desktop or mobile toggle
    share card        one per platform
```

## API

### SeoPreview

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `SeoMeta` | none | Controlled meta. |
| `defaultValue` | `SeoMeta` | empty | Initial meta when uncontrolled. |
| `onValueChange` | `(value: SeoMeta) => void` | none | Turns on the editing fields; called on every change. |
| `defaultPlatform` | `SeoPreviewPlatform` | `"google"` | First tab. |
| `editable` | `boolean` | with `onValueChange` | Show the fields without a callback. |
| `title / description` | `ReactNode` | built-in | Card header. |
| `className` | `string` | none | Extra classes on the root. |
| `labels` | `Partial<SeoPreviewLabels>` | none | Replace any built-in English or Arabic string. |

### SeoMeta

`{ title, description, url, siteName?, favicon?, image?, breadcrumb? }`. `breadcrumb` replaces the trail built from the URL.

### LengthMeter

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `field` | `"title" \| "description"` | required | Which limit to measure against. |
| `text` | `string` | required | The text. |
| `label` | `ReactNode` | field name | Visible name. |
| `labels` | `Partial<SeoPreviewLabels>` | none | Strings. |

### Helpers

`lengthMeter`, `textLength`, `SEO_LIMITS`, `estimatePixelWidth`, `truncateAt`, `pathSegments`, `breadcrumbFor` are exported from the package for use in your own validation. Limits: title 30 to 60 characters, description 70 to 160.

## Examples

Read only, for an audit:

```tsx
import { SeoPreview } from "@fadymondy/nasaq/web";

<SeoPreview value={{ title: "Pricing", description: "Plans for every team.", url: "https://example.com/pricing" }} />;
```

Arabic copy, share tab first:

```tsx
import { SeoPreview } from "@fadymondy/nasaq/web";

<SeoPreview
  defaultPlatform="whatsapp"
  defaultValue={{ title: "دليل تصميم واجهات من اليمين إلى اليسار", description: "تعلّم كيف تبني واجهات تعمل باتجاه اليمين دون ورقة أنماط ثانية.", url: "https://example.com/ar/guide" }}
/>;
```

## Accessibility

| Key | Action |
| --- | --- |
| Arrow keys | Move between platform tabs. |
| Tab | Move to the fields and the desktop / mobile toggle. |

Each meter is a `role="meter"` with a spoken value and a written verdict, so colour is not the only signal. Pass localised `labels`.

## RTL & i18n

Titles and descriptions use `dir="auto"`; the URL and breadcrumb stay left-to-right. Counts use Latin digits. Built-in English and Arabic strings.

## Styling & tokens

Uses `--nq-*` surface, border and status tokens. Meters carry `data-status="empty | short | good | long"`.

## Do / Don't

- Do let the author edit and see the result at once.
- Do pass a real `image` when you have one.
- Don't add logos of the networks: the preview uses names only.

## Related

- [SeoPageList](https://docs.nasaqui.com/components/seo-pages)
- [KeywordTracker](https://docs.nasaqui.com/components/keyword-tracker)

## Lab

https://docs.nasaqui.com/?path=/docs/components-seo-seo-preview--docs

## Code

### React

```tsx
import { useState } from "react";
import { type SeoMeta, SeoPreview } from "@fadymondy/nasaq/web";

export function PageSeo() {
  const [meta, setMeta] = useState<SeoMeta>({
    title: "A complete guide to RTL design systems",
    description: "Learn how to build interfaces that work from right to left without a second stylesheet.",
    url: "https://example.com/blog/rtl-guide",
  });
  return <SeoPreview value={meta} onValueChange={setMeta} />;
}
```

### shadcn

```tsx
import { useState } from "react";
import { type SeoMeta, SeoPreview } from "@/components/ui/seo-preview";

export function PageSeo() {
  const [meta, setMeta] = useState<SeoMeta>({
    title: "A complete guide to RTL design systems",
    description: "Learn how to build interfaces that work from right to left without a second stylesheet.",
    url: "https://example.com/blog/rtl-guide",
  });
  return <SeoPreview value={meta} onValueChange={setMeta} />;
}
```

### Vue

```vue
<script setup lang="ts">
import { ref } from "vue";
import { NqSeoPreview, type SeoMeta } from "@fadymondy/nasaq/vue";

const meta = ref<SeoMeta>({
  title: "A complete guide to RTL design systems",
  description: "Learn how to build interfaces that work from right to left without a second stylesheet.",
  url: "https://example.com/blog/rtl-guide",
});
</script>

<template>
  <NqSeoPreview v-model="meta" />
</template>
```

### Blade

```blade
<x-nq::seo-preview editable :meta="[
    'title' => 'A practical guide to right-to-left design systems',
    'description' => 'How to build interfaces that read naturally in Arabic and English, with logical properties, mirrored icons and bidi-safe numbers.',
    'url' => 'https://nasaq.dev/blog/rtl-design-systems',
    'siteName' => 'Nasaq',
]" />
```

### HTML + Alpine

```html
<div data-slot="seo-preview" class="flex flex-col gap-4 rounded-card border border-border bg-card py-4 text-card-foreground"><div data-slot="card-header" class="grid auto-rows-min items-start gap-1 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]"><h3 data-slot="card-title" class="text-label text-foreground">SEO preview</h3>
        <div data-slot="card-description" class="text-body-sm text-muted-foreground">How this page looks in search results and when it is shared.</div></div>
    <div data-slot="card-content" class="px-4 flex flex-col gap-5"><div x-data="nqSeoPreview(JSON.parse('{\u0022title\u0022:\u0022A practical guide to right-to-left design systems\u0022,\u0022description\u0022:\u0022How to build interfaces that read naturally in Arabic and English, with logical properties, mirrored icons and bidi-safe numbers.\u0022,\u0022url\u0022:\u0022https:\\\/\\\/nasaq.dev\\\/blog\\\/rtl-design-systems\u0022,\u0022siteName\u0022:\u0022Nasaq\u0022}'), JSON.parse('{\u0022length\u0022:\u0022{n} of {max} characters\u0022,\u0022empty\u0022:\u0022Empty\u0022,\u0022short\u0022:\u0022Too short\u0022,\u0022good\u0022:\u0022Good length\u0022,\u0022longOne\u0022:\u00221 character too long\u0022,\u0022longMany\u0022:\u0022{n} characters too long\u0022,\u0022noTitle\u0022:\u0022Untitled page\u0022,\u0022noDescription\u0022:\u0022No description. Search engines will pick text from the page.\u0022,\u0022siteFallback\u0022:\u0022Your site\u0022}'))" class="flex flex-col gap-5">
            <div data-slot="tabs" data-orientation="horizontal" x-data="nqTabs('google')" x-modelable="value" x-id="['nq-tabs']" class="flex flex-col gap-4">
    <div data-slot="tabs-list" data-variant="underline" aria-orientation="horizontal" x-bind="list"
    class="relative z-0 flex max-w-full overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden gap-4 border-b border-border">
    <button type="button" data-slot="tabs-tab" x-bind="tab('google')"
     data-active aria-selected="true"         class="inline-flex shrink-0 items-center gap-1.5 whitespace-nowrap text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground data-active:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 h-9 px-0.5">Google</button>
                                            <button type="button" data-slot="tabs-tab" x-bind="tab('open-graph')"
            class="inline-flex shrink-0 items-center gap-1.5 whitespace-nowrap text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground data-active:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 h-9 px-0.5">Open Graph</button>
                                            <button type="button" data-slot="tabs-tab" x-bind="tab('x')"
            class="inline-flex shrink-0 items-center gap-1.5 whitespace-nowrap text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground data-active:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 h-9 px-0.5">X</button>
                                            <button type="button" data-slot="tabs-tab" x-bind="tab('whatsapp')"
            class="inline-flex shrink-0 items-center gap-1.5 whitespace-nowrap text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground data-active:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 h-9 px-0.5">WhatsApp</button>
                                            <button type="button" data-slot="tabs-tab" x-bind="tab('linkedin')"
            class="inline-flex shrink-0 items-center gap-1.5 whitespace-nowrap text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground data-active:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 h-9 px-0.5">LinkedIn</button>
                                        <span data-slot="tabs-indicator" aria-hidden="true"
    class="absolute -z-10 transition-[left,width] duration-200 ease-nq left-[var(--active-tab-left)] w-[var(--active-tab-width)] bottom-0 h-0.5 rounded-full bg-primary"></span>
</div>
                <div data-slot="tabs-panel" x-bind="panel('google')"
        class="outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus flex flex-col gap-3 pt-4"><div role="group" data-slot="toggle-group" data-variant="segmented" data-orientation="horizontal"
    x-data="nqToggleGroup(JSON.parse('[\u0022desktop\u0022]'), false)" x-modelable="value" x-bind="root"
            x-model="device" aria-label="Device" class="flex w-fit max-w-full gap-0.5 rounded-control bg-secondary p-0.5">
    <button type="button" data-slot="toggle" aria-pressed="true"
     x-bind="toggle('desktop')"      data-pressed         class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="20" height="14" x="2" y="3" rx="2"/>
  <line x1="8" x2="16" y1="21" y2="21"/>
  <line x1="12" x2="12" y1="17" y2="21"/>
</svg>Desktop</button>
                        <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('mobile')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="14" height="20" x="5" y="2" rx="2" ry="2"/>
  <path d="M12 18h.01"/>
</svg>Mobile</button>
</div>
                    <div data-slot="seo-google" x-bind:data-device="mobile ? 'mobile' : 'desktop'" data-device="desktop"
                        x-bind:class="mobile ? 'w-full max-w-[22rem]' : 'max-w-[41rem]'" class="flex max-w-[41rem] flex-col gap-1 rounded-card border border-border bg-card p-4">
                        <div class="flex items-center gap-2.5">
                            <img x-show="favicon" x-bind:src="favicon || null" alt="" class="size-7 shrink-0 rounded-full border border-border" style="display: none">
                            <span x-show="! favicon" aria-hidden="true" x-text="firstLetter" class="flex size-[18px] shrink-0 items-center justify-center rounded-full bg-secondary text-[10px] font-semibold uppercase text-muted-foreground size-7 border border-border"></span>
                            <div class="flex min-w-0 flex-col leading-tight">
                                <span class="truncate text-body-sm text-foreground" dir="auto" x-text="site"></span>
                                <span dir="ltr" class="flex min-w-0 items-center gap-1 text-caption text-muted-foreground">
                                    <template x-for="(c, i) in crumbs" x-bind:key="i + c">
                                        <span class="flex min-w-0 items-center gap-1">
                                            <svg aria-hidden="true" x-show="i > 0" class="size-3 shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m9 18 6-6-6-6"/>
</svg>                                            <span class="truncate" x-text="c"></span>
                                        </span>
                                    </template>
                                </span>
                            </div>
                        </div>
                        <p dir="auto" x-bind:class="mobile ? 'line-clamp-2 text-body' : 'line-clamp-1 text-h5'" x-text="googleTitle" class="mt-1 line-clamp-1 text-h5 font-medium text-nq-info-text"></p>
                        <p dir="auto" x-bind:class="mobile ? 'line-clamp-3' : 'line-clamp-2'" x-text="googleDescription" class="line-clamp-2 text-body-sm text-muted-foreground"></p>
                    </div></div>
                <div data-slot="tabs-panel" x-bind="panel('open-graph')"
     hidden     class="outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus pt-4"><div data-slot="seo-open-graph" class="w-full max-w-[32rem] overflow-hidden rounded-card border border-border bg-card">
                        <img x-show="image" x-bind:src="image || null" alt="" class="aspect-[1.91/1] w-full object-cover" style="display: none">
                        <div x-show="! image" class="flex flex-col items-center justify-center gap-1 bg-secondary text-caption text-muted-foreground aspect-[1.91/1] w-full"><svg aria-hidden="true" class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="18" height="18" x="3" y="3" rx="2" ry="2"/>
  <circle cx="9" cy="9" r="2"/>
  <path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>
</svg>No share image</div>
                        <div class="flex flex-col gap-0.5 border-t border-border bg-secondary/50 p-3">
                            <span dir="ltr" class="truncate text-start text-caption uppercase text-muted-foreground" x-text="host"></span>
                            <p dir="auto" class="line-clamp-2 text-label text-foreground" x-text="shownTitle"></p>
                            <p dir="auto" class="line-clamp-2 text-caption text-muted-foreground" x-text="shownDescription"></p>
                        </div>
                    </div></div>
                <div data-slot="tabs-panel" x-bind="panel('x')"
     hidden     class="outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus pt-4"><div data-slot="seo-x" class="w-full max-w-[32rem]">
                        <div class="relative overflow-hidden rounded-2xl border border-border bg-card">
                            <img x-show="image" x-bind:src="image || null" alt="" class="aspect-[2/1] w-full object-cover" style="display: none">
                            <div x-show="! image" class="flex flex-col items-center justify-center gap-1 bg-secondary text-caption text-muted-foreground aspect-[2/1] w-full"><svg aria-hidden="true" class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="18" height="18" x="3" y="3" rx="2" ry="2"/>
  <circle cx="9" cy="9" r="2"/>
  <path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>
</svg>No share image</div>
                            <span dir="auto" class="absolute bottom-2 start-2 max-w-[80%] truncate rounded-md bg-foreground/70 px-1.5 py-0.5 text-caption text-background" x-text="shownTitle"></span>
                        </div>
                        <p dir="ltr" class="mt-1 px-1 text-start text-caption text-muted-foreground" x-text="host"></p>
                    </div></div>
                <div data-slot="tabs-panel" x-bind="panel('whatsapp')"
     hidden     class="outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus pt-4"><div data-slot="seo-whatsapp" class="flex w-full max-w-[24rem] flex-col gap-1 rounded-card bg-secondary p-2">
                        <div class="overflow-hidden rounded-md bg-card">
                            <img x-show="image" x-bind:src="image || null" alt="" class="aspect-[1.91/1] w-full object-cover" style="display: none">
                            <div x-show="! image" class="flex flex-col items-center justify-center gap-1 bg-secondary text-caption text-muted-foreground aspect-[1.91/1] w-full"><svg aria-hidden="true" class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="18" height="18" x="3" y="3" rx="2" ry="2"/>
  <circle cx="9" cy="9" r="2"/>
  <path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>
</svg>No share image</div>
                            <div class="flex flex-col gap-0.5 p-2.5">
                                <p dir="auto" class="line-clamp-2 text-label text-foreground" x-text="shownTitle"></p>
                                <p dir="auto" class="line-clamp-2 text-caption text-muted-foreground" x-text="shownDescription"></p>
                                <span dir="ltr" class="truncate text-start text-caption text-muted-foreground" x-text="host"></span>
                            </div>
                        </div>
                        <span dir="ltr" class="truncate px-1 text-start text-caption text-nq-info-text" x-text="url"></span>
                    </div></div>
                <div data-slot="tabs-panel" x-bind="panel('linkedin')"
     hidden     class="outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus pt-4"><div data-slot="seo-linkedin" class="w-full max-w-[32rem] overflow-hidden rounded-card border border-border bg-card">
                        <img x-show="image" x-bind:src="image || null" alt="" class="aspect-[1.91/1] w-full object-cover" style="display: none">
                        <div x-show="! image" class="flex flex-col items-center justify-center gap-1 bg-secondary text-caption text-muted-foreground aspect-[1.91/1] w-full"><svg aria-hidden="true" class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="18" height="18" x="3" y="3" rx="2" ry="2"/>
  <circle cx="9" cy="9" r="2"/>
  <path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>
</svg>No share image</div>
                        <div class="flex flex-col gap-0.5 p-3">
                            <p dir="auto" class="line-clamp-2 text-label text-foreground" x-text="shownTitle"></p>
                            <span dir="ltr" class="truncate text-start text-caption text-muted-foreground" x-text="host"></span>
                        </div>
                    </div></div>
</div>
            <div class="grid gap-4 md:grid-cols-2">
                                    <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Title</label>
                        <input data-slot="input" type="text"
                    dir="auto" x-model="title" x-on:input="changed()" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control">
</div>
                    <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Page URL</label>
                        <input data-slot="input" type="text"
     dir="ltr"                 x-model="url" x-on:input="changed()" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control text-start">
</div>
                    <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     class="flex flex-col gap-1.5 md:col-span-2">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Meta description</label>
                        <textarea data-slot="textarea"
                dir="auto" rows="3" x-model="description" x-on:input="changed()" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] min-h-20 py-2"></textarea>
</div>
                    <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     class="flex flex-col gap-1.5 md:col-span-2">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Share image URL</label>
                        <input data-slot="input" type="text"
     dir="ltr"                 x-model="image" x-on:input="changed()" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control text-start">
</div>
                                                    <div data-slot="length-meter" x-bind:data-status="titleMeter.status" class="flex flex-col gap-1.5">
                        <div class="flex items-baseline justify-between gap-3 text-caption">
                            <span class="min-w-0 truncate text-muted-foreground">Title</span>
                            <bdi class="shrink-0 tabular-nums text-muted-foreground" x-text="titleMeter.text"></bdi>
                        </div>
                        <div data-slot="meter" role="meter" aria-label="Title length" aria-valuemin="0" x-bind:aria-valuemax="titleMeter.max" x-bind:aria-valuenow="titleMeter.length" x-bind:aria-valuetext="titleMeter.text" x-bind:data-tone="titleMeter.tone === 'neutral' ? 'default' : titleMeter.tone" class="flex w-full flex-col gap-1.5">
                            <div data-slot="meter-track" class="relative block h-1 w-full overflow-hidden rounded-full bg-nq-surface-soft">
                                <div data-slot="meter-indicator" style="inset-inline-start:0;width:0%" x-bind:style="'inset-inline-start:0;width:' + titleMeter.percent + '%'"
                                    x-bind:class="{  'bg-primary': titleMeter.tone === 'neutral',  'bg-nq-warning': titleMeter.tone === 'warning',  'bg-nq-success': titleMeter.tone === 'success',  'bg-nq-danger': titleMeter.tone === 'danger',  }" class="block h-full rounded-full bg-primary transition-[width] duration-300 ease-nq motion-reduce:transition-none"></div>
                            </div>
                        </div>
                        <span data-slot="status" x-bind:data-tone="titleMeter.tone" class="inline-flex min-w-0 items-center gap-1.5 text-body-sm text-caption"
                            x-bind:class="{  'text-muted-foreground': titleMeter.tone === 'neutral',  'text-nq-warning-text': titleMeter.tone === 'warning',  'text-nq-success-text': titleMeter.tone === 'success',  'text-nq-danger-text': titleMeter.tone === 'danger',  }"><span class="truncate" x-text="titleMeter.verdict"></span></span>
                    </div>
                                    <div data-slot="length-meter" x-bind:data-status="descMeter.status" class="flex flex-col gap-1.5">
                        <div class="flex items-baseline justify-between gap-3 text-caption">
                            <span class="min-w-0 truncate text-muted-foreground">Meta description</span>
                            <bdi class="shrink-0 tabular-nums text-muted-foreground" x-text="descMeter.text"></bdi>
                        </div>
                        <div data-slot="meter" role="meter" aria-label="Meta description length" aria-valuemin="0" x-bind:aria-valuemax="descMeter.max" x-bind:aria-valuenow="descMeter.length" x-bind:aria-valuetext="descMeter.text" x-bind:data-tone="descMeter.tone === 'neutral' ? 'default' : descMeter.tone" class="flex w-full flex-col gap-1.5">
                            <div data-slot="meter-track" class="relative block h-1 w-full overflow-hidden rounded-full bg-nq-surface-soft">
                                <div data-slot="meter-indicator" style="inset-inline-start:0;width:0%" x-bind:style="'inset-inline-start:0;width:' + descMeter.percent + '%'"
                                    x-bind:class="{  'bg-primary': descMeter.tone === 'neutral',  'bg-nq-warning': descMeter.tone === 'warning',  'bg-nq-success': descMeter.tone === 'success',  'bg-nq-danger': descMeter.tone === 'danger',  }" class="block h-full rounded-full bg-primary transition-[width] duration-300 ease-nq motion-reduce:transition-none"></div>
                            </div>
                        </div>
                        <span data-slot="status" x-bind:data-tone="descMeter.tone" class="inline-flex min-w-0 items-center gap-1.5 text-body-sm text-caption"
                            x-bind:class="{  'text-muted-foreground': descMeter.tone === 'neutral',  'text-nq-warning-text': descMeter.tone === 'warning',  'text-nq-success-text': descMeter.tone === 'success',  'text-nq-danger-text': descMeter.tone === 'danger',  }"><span class="truncate" x-text="descMeter.verdict"></span></span>
                    </div>
                            </div>
        </div></div></div>
```
