{"id":129,"date":"2026-08-28T22:26:13","date_gmt":"2026-08-28T22:26:13","guid":{"rendered":"https:\/\/seoedgeai.com\/blog\/meta-title-description-checker\/"},"modified":"2026-08-28T22:26:23","modified_gmt":"2026-08-28T22:26:23","slug":"meta-title-description-checker","status":"publish","type":"page","link":"https:\/\/seoedgeai.com\/blog\/meta-title-description-checker\/","title":{"rendered":"SEO Title Length Checker with SERP Preview"},"content":{"rendered":"<p><!doctype html><br \/>\n<html lang=\"en\"><br \/>\n<head><br \/>\n<meta charset=\"utf-8\"><br \/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><br \/>\n<title>SEO Title Length Checker with SERP Preview | SEOEdgeAI<\/title><\/p>\n<style>\n  :root{\n    --bg:#faf9f4; --card:#ffffff; --ink:#1c1b18; --muted:#6b6a63;\n    --line:#e6e4da; --green:#0a7d46; --green-bg:#e3f7eb; --amber:#9a5b00; --amber-bg:#fdf1da;\n    --red:#b3261e; --red-bg:#fdeceb; --blue:#1a0dab; --url:#0a6b3d; --grey:#4d5156;\n    --radius:14px;\n  }\n  *{box-sizing:border-box}\n  html,body{margin:0;padding:0}\n  body{\n    background:var(--bg); color:var(--ink);\n    font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;\n    line-height:1.6; -webkit-font-smoothing:antialiased;\n  }\n  .wrap{max-width:780px;margin:0 auto;padding:28px 18px 64px}\n  .eyebrow{font-size:13px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin:0 0 6px;font-weight:600}\n  h1{font-size:clamp(26px,5vw,38px);line-height:1.15;margin:0 0 14px;letter-spacing:-.01em}\n  .lede{font-size:17px;color:#44423c;margin:0 0 26px;max-width:640px}\n  a{color:#1445b3;text-decoration:underline;text-underline-offset:2px}\n  a:hover{color:#0b2f80}<\/p>\n<p>  \/* Tool card *\/\n  .tool{background:var(--card);border:1px solid var(--line);border-radius:18px;padding:24px;\n        box-shadow:0 1px 2px rgba(20,20,16,.05);margin-bottom:18px}\n  .tool h2{margin:0 0 4px;font-size:19px}\n  .tool .sub{color:var(--muted);font-size:14px;margin:0 0 16px}\n  .fields{display:grid;gap:14px}\n  .fields label{display:block;font-size:14px;font-weight:600;color:#33322d}\n  .fields label .count{margin-left:8px;font-weight:500;color:var(--muted);font-size:12px}\n  input,textarea{\n    display:block;width:100%;margin-top:6px;padding:11px 12px;font-size:15px;\n    border:1px solid #d9d7cc;border-radius:10px;background:#fff;color:var(--ink);font-family:inherit;\n  }\n  input:focus,textarea:focus{outline:none;border-color:#1445b3;box-shadow:0 0 0 3px rgba(20,69,179,.12)}\n  textarea{resize:vertical;min-height:74px}\n  .row2{display:grid;grid-template-columns:1fr 1fr;gap:14px}\n  @media(max-width:560px){.row2{grid-template-columns:1fr}}\n  .actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:16px}\n  button{\n    font-family:inherit;font-size:14px;font-weight:600;border-radius:10px;cursor:pointer;\n    padding:10px 16px;border:1px solid #d9d7cc;background:#fff;color:var(--ink);transition:background .12s;\n  }\n  button:hover{background:#f4f2ea}\n  button.primary{background:#1445b3;border-color:#1445b3;color:#fff}\n  button.primary:hover{background:#0b2f80}\n  button:focus-visible{outline:3px solid rgba(20,69,179,.35);outline-offset:1px}<\/p>\n<p>  \/* Verdicts *\/\n  .verdicts{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-top:18px}\n  @media(max-width:560px){.verdicts{grid-template-columns:1fr}}\n  .v{border:1px solid var(--line);border-radius:12px;padding:12px 14px;background:#fcfbf7}\n  .v .vtop{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:4px}\n  .v .vname{font-weight:700;font-size:14px}\n  .v .vstats{font-size:12px;color:var(--muted)}\n  .v .vmsg{font-size:13px;margin-top:6px}\n  .badge{font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;padding:3px 8px;border-radius:999px;white-space:nowrap}\n  .badge.pass{background:var(--green-bg);color:var(--green)}\n  .badge.warn{background:var(--amber-bg);color:var(--amber)}\n  .badge.fail{background:var(--red-bg);color:var(--red)}\n  .badge.info{background:#eef0f6;color:#3c4870}\n  .v.pass{border-left:4px solid var(--green)}\n  .v.warn{border-left:4px solid var(--amber)}\n  .v.fail{border-left:4px solid var(--red)}<\/p>\n<p>  \/* Keyword strip *\/\n  .kw{margin-top:12px;font-size:14px;border-top:1px dashed var(--line);padding-top:12px;display:flex;gap:8px;flex-wrap:wrap;align-items:center}\n  .chip{font-size:12.5px;font-weight:600;padding:5px 10px;border-radius:999px;background:#f0efe8;color:#4a4a44}\n  .chip.ok{background:var(--green-bg);color:var(--green)}\n  .chip.bad{background:var(--red-bg);color:var(--red)}\n  .chip.soft{background:var(--amber-bg);color:var(--amber)}<\/p>\n<p>  \/* Previews *\/\n  .previews{display:flex;flex-direction:column;gap:14px;margin-top:16px}\n  .preview{border:1px solid var(--line);border-radius:14px;padding:10px 12px 12px;background:#fff;max-width:640px}\n  .preview.mobile{max-width:400px}\n  .preview .ph{font-size:11.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);margin-bottom:2px;display:flex;justify-content:space-between;gap:8px}\n  .preview .ph span{font-weight:500;letter-spacing:0;text-transform:none}\n  .serp{padding:12px 6px 4px}\n  .serp .pv-title{color:var(--blue);font-size:20px;line-height:1.3;white-space:nowrap;overflow:hidden;font-weight:400}\n  .serp .pv-url{color:var(--url);font-size:14px;margin-top:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .serp .pv-desc{color:var(--grey);font-size:14px;line-height:1.55;margin-top:2px;white-space:normal;overflow:hidden}\n  .serp.mob .pv-desc{font-size:16px}\n  .pv-note{font-size:12px;color:var(--muted);margin-top:8px;border-top:1px dashed var(--line);padding-top:8px}<\/p>\n<p>  \/* Article *\/\n  .explain{margin-top:34px}\n  .explain h2{font-size:24px;margin:34px 0 10px;letter-spacing:-.01em;line-height:1.2}\n  .explain h3{font-size:18px;margin:26px 0 8px}\n  .explain p, .explain li{font-size:16px;color:#33322e}\n  .explain ul,.explain ol{padding-left:22px}\n  .explain li{margin:6px 0}\n  .explain figure{margin:22px 0 6px}\n  .explain img{width:100%;border-radius:12px;border:1px solid var(--line);display:block}\n  .explain figcaption{font-size:12.5px;color:var(--muted);margin-top:6px}\n  .video-wrap{margin:22px 0 6px}\n  .video-wrap iframe{width:100%;aspect-ratio:16\/9;border:0;border-radius:12px;display:block}\n  .video-note{font-size:12.5px;color:var(--muted);margin-top:6px}\n  .next{margin-top:40px;padding:18px 20px;background:var(--card);border:1px solid var(--line);border-radius:14px}\n  .next p{margin:6px 0}\n  .note{font-size:12.5px;color:var(--muted);margin-top:14px}\n  .sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}\n<\/style>\n<p><\/head><br \/>\n<body><br \/>\n<main class=\"wrap\"><\/p>\n<header>\n<p class=\"eyebrow\">Free SEO tool<\/p>\n<h1>SEO Title Length Checker with SERP Preview<\/h1>\n<p class=\"lede\">Paste your page title and meta description, add the keyword you want to check, and see how Google renders them on desktop and mobile: character counts, an estimate of the rendered pixel width, where the text gets cut off, and what to fix before you publish.<\/p>\n<\/header>\n<section class=\"tool\" aria-label=\"Title and meta description checker\">\n<h2>Check your metadata<\/h2>\n<p class=\"sub\">Everything is computed in your browser. Nothing is sent anywhere.<\/p>\n<form id=\"f\">\n<div class=\"fields\">\n        <label for=\"i-title\">Page title <span class=\"count\" id=\"title-count\">0 \/ 80<\/span><br \/>\n          <input id=\"i-title\" maxlength=\"80\" autocomplete=\"off\" placeholder=\"e.g. Meta Title and Description Checker | SEOEdgeAI\"><br \/>\n        <\/label><br \/>\n        <label for=\"i-desc\">Meta description <span class=\"count\" id=\"desc-count\">0 \/ 220<\/span><br \/>\n          <textarea id=\"i-desc\" maxlength=\"220\" rows=\"3\" placeholder=\"One or two sentences that tell a searcher what the page is about and why to click.\"><\/textarea><br \/>\n        <\/label><\/p>\n<div class=\"row2\">\n          <label for=\"i-kw\">Primary keyword <span class=\"count\">optional<\/span><br \/>\n            <input id=\"i-kw\" autocomplete=\"off\" placeholder=\"e.g. seo title length checker\"><br \/>\n          <\/label><br \/>\n          <label for=\"i-url\">Display URL <span class=\"count\">optional<\/span><br \/>\n            <input id=\"i-url\" autocomplete=\"off\" placeholder=\"https:\/\/example.com\/page\/\"><br \/>\n          <\/label>\n        <\/div>\n<\/p><\/div>\n<div class=\"actions\">\n        <button type=\"button\" id=\"btn-example\">Load an example<\/button><br \/>\n        <button type=\"button\" class=\"primary\" id=\"btn-copy\">Copy preview block<\/button>\n      <\/div>\n<\/p><\/form>\n<div class=\"verdicts\" id=\"verdicts\" aria-live=\"polite\"><\/div>\n<div class=\"kw\" id=\"kw\" aria-live=\"polite\"><\/div>\n<div class=\"previews\">\n<div class=\"preview\">\n<div class=\"ph\">Desktop preview <span>title cut-off \u2248 600 px<\/span><\/div>\n<div class=\"serp\" id=\"pv-desktop\"><\/div>\n<p class=\"pv-note\" id=\"note-desktop\">Title: 0 chars, \u2248 0 px wide. Nothing to preview yet.<\/p>\n<\/p><\/div>\n<div class=\"preview mobile\">\n<div class=\"ph\">Mobile preview <span>title cut-off \u2248 350 px<\/span><\/div>\n<div class=\"serp mob\" id=\"pv-mobile\"><\/div>\n<p class=\"pv-note\" id=\"note-mobile\">Description: 0 chars. Nothing to preview yet.<\/p>\n<\/p><\/div>\n<\/p><\/div>\n<p class=\"note\">Counts are characters. Pixel widths are measured at the sizes Google uses in results (20&nbsp;px titles, ~14&nbsp;px snippets). The \u2248600&nbsp;px desktop and \u2248350&nbsp;px mobile cut-offs are community-measured estimates, not published limits: Google says it truncates titles and snippets to fit the device width, and does not set a character limit.<\/p>\n<\/section>\n<article class=\"explain\">\n<h2>What Google actually does with your title and description<\/h2>\n<p>Before anything else, one fact explains every number above: Google does not publish a character limit for titles or meta descriptions. Its own documentation says a title link is &ldquo;truncated in Google Search results as needed, typically to fit the device width&rdquo;, and that there is &ldquo;no limit on how long a meta description can be&rdquo; for the same reason. Whatever the search engine decides to show is a rendering decision, not a rule you passed or failed.<\/p>\n<p>Because fit is the test, rendering is measured in pixels, not characters. SEO tooling has measured that cut-off for years: titles that exceed roughly 600&nbsp;pixels get truncated or rewritten on desktop, and the usable width on a phone is closer to 350&nbsp;pixels, which is why titles that fit fine on a monitor get cut short on mobile.<\/p>\n<figure>\n      <img decoding=\"async\" width=\"2560\" height=\"1707\" class=\"wp-image-130\" src=\"https:\/\/seoedgeai.com/blog\/wp-content\/uploads\/sites\/4\/2026\/08\/pexels-photo-6986455-3-scaled.jpeg\" alt=\"A laptop showing a Google search results page with blue result titles, green URLs and grey snippets\" loading=\"lazy\" srcset=\"https:\/\/seoedgeai.com\/blog\/wp-content\/uploads\/sites\/4\/2026\/08\/pexels-photo-6986455-3-scaled.jpeg 2560w, https:\/\/seoedgeai.com\/blog\/wp-content\/uploads\/sites\/4\/2026\/08\/pexels-photo-6986455-3-300x200.jpeg 300w, https:\/\/seoedgeai.com\/blog\/wp-content\/uploads\/sites\/4\/2026\/08\/pexels-photo-6986455-3-1024x683.jpeg 1024w, https:\/\/seoedgeai.com\/blog\/wp-content\/uploads\/sites\/4\/2026\/08\/pexels-photo-6986455-3-768x512.jpeg 768w, https:\/\/seoedgeai.com\/blog\/wp-content\/uploads\/sites\/4\/2026\/08\/pexels-photo-6986455-3-1536x1024.jpeg 1536w, https:\/\/seoedgeai.com\/blog\/wp-content\/uploads\/sites\/4\/2026\/08\/pexels-photo-6986455-3-2048x1365.jpeg 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><figcaption>Photo: cottonbro, Pexels License, via Pexels.<\/figcaption><\/figure>\n<p>Here is the part that breaks character-count advice. In a title rendered at 20&nbsp;px, an uppercase &ldquo;W&rdquo; is several times as wide as a lowercase &ldquo;i&rdquo;. A 60-character title made of narrow letters can fit under the desktop cut-off, while a 52-character title full of capital letters overflows it. The same is true on the snippet side: an &ldquo;m&rdquo; eats noticeably more space than an &ldquo;l&rdquo;, so two descriptions of identical length will not truncate at the same point. This is why the checker measures your actual text instead of applying a flat characters-per-pixel estimate, and why its replies are framed as &ldquo;fits&rdquo; or &ldquo;may be cut&rdquo; rather than as a precise prediction.<\/p>\n<h2>Where the 60-character title and 120 to 160 description ranges come from<\/h2>\n<p>The ranges have always been rules of thumb derived from pixel limits, and the math behind them is easy to see once the width numbers are on the table:<\/p>\n<ul>\n<li><strong>Titles.<\/strong> At 600&nbsp;px and mixed-case text of average width, roughly 60 to 70 characters fit on desktop, and the 350&nbsp;px mobile cut-off lands nearer 30 to 45. Keeping a title to about 60 characters is still a sane target; treating 60 as a hard ceiling, or assuming it applies to every screen, is not.<\/li>\n<li><strong>Meta descriptions.<\/strong> Yoast&rsquo;s testing found that snippets visible in Google&rsquo;s results usually run 120 to 156 characters. Semrush&rsquo;s 2025 study of real results measured an average snippet length of 146 characters on desktop and 136 on mobile. That is the empirical basis for the 120 to 160 window this tool checks: above it you risk a truncated sentence ending in &ldquo;&hellip;&rdquo;, and well below it you are leaving persuasive space unused.<\/li>\n<\/ul>\n<h2>What the verdicts mean<\/h2>\n<p><strong>Title.<\/strong> Pass means the text fits both the \u2248600&nbsp;px desktop estimate and the \u2248350&nbsp;px mobile estimate. Warn means it fits desktop but is likely to be cut on a phone, or it is over the common 60-character guideline. Fail means it is wide enough that Google will probably rewrite or truncate it even on desktop, which also risks the title your CMS wanted being replaced entirely.<\/p>\n<p><strong>Meta description.<\/strong> Pass means the length sits in the 120 to 160 character zone. Warn under 120 means you are not using the space in the results; warn above 160 means the last words you wrote are likely to be invisible. An empty description is shown as fail for a different reason: Google will compose its own snippet from your page content, and you lose control of what the searcher reads before clicking.<\/p>\n<h2>Why the keyword check is not decoration<\/h2>\n<p>Google bolds the terms in your snippet that match the query, and a result whose title carries the search phrase reads as more relevant. Semrush and Yoast both make the same practical recommendation this tool encodes: put the primary keyword in the title, ideally near the front, and if it can be worked in naturally, in the meta description too. The checker flags a keyword that is missing from either, so you catch it before the page ships rather than after it underperforms.<\/p>\n<h2>What people most often get wrong<\/h2>\n<ul>\n<li><strong>&ldquo;My title is 58 characters, so I am safe.&rdquo;<\/strong> Character count ignores letter width and device. A 58-character title with wide glyphs can still push past the desktop cut-off, and almost any 58-character sentence occupies more than half the mobile width.<\/li>\n<li><strong>&ldquo;There is a hard 160-character description limit.&rdquo;<\/strong> There is not. Google may show slightly more or less depending on width and query, and it may rewrite your description entirely. 160 is the point where truncation becomes likely, not a door that slams shut.<\/li>\n<li><strong>&ldquo;Google will show what I wrote.&rdquo;<\/strong> Often it will not. Google&rsquo;s snippet documentation says snippets are &ldquo;primarily created from the page content itself&rdquo;, and Semrush&rsquo;s 2025 sample found the meta description being used in only about 28% of the results they checked. A good description still matters, because when your wording does get used, it earns the click; but expecting it to always appear is a mistake.<\/li>\n<li><strong>&ldquo;All characters are the same width.&rdquo;<\/strong> This is the assumption behind every fixed &ldquo;chars per line&rdquo; calculator, and it is wrong enough to matter, which is why this checker measures the rendered width of your actual text.<\/li>\n<\/ul>\n<div class=\"video-wrap\">\n      <iframe src=\"https:\/\/www.youtube.com\/embed\/9LMc75qLKXc\" title=\"How Google wants you to create meta descriptions, by Neil Patel\" loading=\"lazy\" allowfullscreen><\/iframe><\/p>\n<p class=\"video-note\">How Google Wants You to Create Meta Descriptions, Neil Patel on YouTube, covers how Google decides which snippet to show and what to put in a description so yours gets used.<\/p>\n<\/p><\/div>\n<h2>A fast checklist before you publish<\/h2>\n<ol>\n<li>Write the title with the primary keyword in the first half, under about 60 characters, and check both previews above. If the mobile preview cuts a word you need, shorten it.<\/li>\n<li>Write the description in the 120 to 160 zone, in the active voice, with the keyword and one concrete reason to click. Preview it at both widths.<\/li>\n<li>Confirm the keyword flag shows it present in the title and ideally in the description. If either is missing, decide whether the phrase can be worked in without making the text read badly.<\/li>\n<li>Check the exact pages, not the template: titles and descriptions are often generated per page, and one formula does not fit every URL.<\/li>\n<\/ol>\n<p class=\"note\">Sources: Google Search Central on <a href=\"https:\/\/developers.google.com\/search\/docs\/appearance\/title-link\" target=\"_blank\" rel=\"noopener\">title links<\/a> and on <a href=\"https:\/\/developers.google.com\/search\/docs\/appearance\/snippet\" target=\"_blank\" rel=\"noopener\">snippets<\/a>, <a href=\"https:\/\/ahrefs.com\/blog\/title-tag-seo\/\" target=\"_blank\" rel=\"noopener\">Ahrefs&rsquo; title tag guide<\/a>, <a href=\"https:\/\/yoast.com\/meta-descriptions\/\" target=\"_blank\" rel=\"noopener\">Yoast on meta descriptions<\/a>, and <a href=\"https:\/\/www.semrush.com\/blog\/meta-description\/\" target=\"_blank\" rel=\"noopener\">Semrush on meta descriptions<\/a>. The \u2248600&nbsp;px desktop and \u2248350&nbsp;px mobile cut-offs are widely used community estimates; Google does not publish them.<\/p>\n<div class=\"next\">\n<p>This is exactly the problem <a href=\"https:\/\/seoedgeai.com\/seo\">SEOEdgeAI<\/a> automates: an agent connected to your Cloudflare site rewrites titles and meta descriptions at the edge so they fit search results, then learns from your Search Console numbers which changes earn more clicks. The mechanics behind that rewrite are in our guide to <a href=\"https:\/\/seoedgeai.com\/blog\/cloudflare-workers-seo-7-practical-examples\/\">7 practical Cloudflare Workers SEO examples<\/a>.<\/p>\n<\/p><\/div>\n<\/article>\n<p><\/main><\/p>\n<p><script>\n(function(){\n  'use strict';\n  function $(id){ return document.getElementById(id); }\n  var canvas = document.createElement('canvas');\n  var ctx = canvas.getContext ? canvas.getContext('2d') : null;\n  var cache = {};<\/p>\n<p>  \/\/ Fallback glyph advances for Arial, in 1\/1000 em, used only where canvas is unavailable.\n  var UP = {A:667,B:667,C:722,D:722,E:667,F:611,G:778,H:722,I:278,J:500,K:667,L:556,M:833,N:722,O:778,P:667,Q:778,R:722,S:667,T:611,U:722,V:667,W:944,X:667,Y:667,Z:611};\n  var LO = {a:556,b:556,c:500,d:556,e:556,f:278,g:556,h:556,i:222,j:222,k:500,l:222,m:833,n:556,o:556,p:556,q:556,r:333,s:500,t:278,u:556,v:500,w:722,x:500,y:500,z:500};\n  var DI = {'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556};\n  var PU = {' ':278,'!':278,'\"':355,'#':556,'$':556,'%':889,'&':667,\"'\":191,'(':333,')':333,'*':389,'+':584,',':278,'-':333,'.':278,'\/':278,':':278,';':278,'<':584,'=':584,'>':584,'?':556,'@':1015,'[':278,'\\\\':278,']':278,'^':469,'_':556,'`':333,'{':333,'|':260,'}':333,'~':584,'\\u2026':300,'\\u00a0':278};\n  function fallbackAdvance(ch){\n    if (UP[ch]) return UP[ch];\n    if (LO[ch]) return LO[ch];\n    if (DI[ch]) return DI[ch];\n    if (PU[ch] !== undefined) return PU[ch];\n    return 500;\n  }\n  function charWidth(ch, pxSize){\n    var key = ch + '\\u0000' + pxSize;\n    if (cache[key] !== undefined) return cache[key];\n    var w = 0;\n    if (ctx) {\n      try { ctx.font = pxSize + 'px arial, sans-serif'; w = ctx.measureText(ch).width; } catch(e) { w = 0; }\n    }\n    if (!w || !isFinite(w)) w = fallbackAdvance(ch) * pxSize \/ 1000;\n    cache[key] = w;\n    return w;\n  }\n  function totalWidth(text, pxSize){\n    var sum = 0;\n    for (var i = 0; i < text.length; i++) sum += charWidth(text.charAt(i), pxSize);\n    return sum;\n  }\n  function truncateSingle(text, pxSize, limit){\n    if (!text) return {shown:'', truncated:false, visible:0};\n    var ellip = charWidth('\\u2026', pxSize);\n    var acc = 0, idx = 0;\n    for (; idx < text.length; idx++) {\n      var next = acc + charWidth(text.charAt(idx), pxSize);\n      var need = (idx < text.length - 1) ? (next + ellip) : next;\n      if (need > limit) break;\n      acc = next;\n    }\n    var truncated = idx < text.length;\n    return {shown: truncated ? text.slice(0, idx) + '\\u2026' : text, truncated: truncated, visible: idx};\n  }\n  function charsFit(text, pxSize, width, lines){\n    if (!text) return 0;\n    var used = 1, lineW = 0, count = 0;\n    for (var i = 0; i < text.length; i++) {\n      var w = charWidth(text.charAt(i), pxSize);\n      if (lineW + w > width) {\n        used++;\n        if (used > lines) break;\n        lineW = w;\n      } else {\n        lineW += w;\n      }\n      count++;\n    }\n    return count;\n  }\n  function descShown(text, pxSize, width, lines){\n    if (!text) return {shown:'', truncated:false, visible:0};\n    var n = charsFit(text, pxSize, width, lines);\n    var truncated = n < text.length;\n    return {shown: truncated ? text.slice(0, n) + '\\u2026' : text, truncated: truncated, visible: n};\n  }\n\n  var SIZE_TITLE = 20, SIZE_DESC_D = 14, SIZE_DESC_M = 16;\n  var LIMIT_D = 600, LIMIT_M = 350;\n  var LINES_D = 2, LINES_M = 3;\n\n  function cleanUrl(u){\n    u = (u || '').trim().replace(\/^https?:\\\/\\\/\/i, '');\n    if (!u) return 'example.com\/page\/';\n    if (u.length > 60) u = u.slice(0, 59) + '\\u2026';\n    return u;\n  }<\/p>\n<p>  function badge(level, word){\n    var el = document.createElement('span');\n    el.className = 'badge ' + level;\n    el.textContent = word;\n    return el;\n  }<\/p>\n<p>  function renderSerp(container, titleShown, url, descShown){\n    var nodes = [];\n    var t = document.createElement('div');\n    t.className = 'pv-title';\n    t.textContent = titleShown || 'Your page title';\n    nodes.push(t);\n    var u = document.createElement('div');\n    u.className = 'pv-url';\n    u.textContent = url;\n    nodes.push(u);\n    var d = document.createElement('div');\n    d.className = 'pv-desc';\n    d.textContent = descShown || 'A short, accurate description of what this page is about appears here in the search results.';\n    nodes.push(d);\n    container.textContent = '';\n    nodes.forEach(function(n){ container.appendChild(n); });\n  }<\/p>\n<p>  function update(){\n    var title = $('i-title').value;\n    var desc = $('i-desc').value;\n    var kw = $('i-kw').value.trim();\n    var url = $('i-url').value.trim();\n    var titleLen = title.length;\n    var descLen = desc.length;<\/p>\n<p>    $('title-count').textContent = titleLen + ' \/ 80';\n    $('desc-count').textContent = descLen + ' \/ 220';<\/p>\n<p>    var titleW = totalWidth(title, SIZE_TITLE);\n    var dT = truncateSingle(title, SIZE_TITLE, LIMIT_D);\n    var mT = truncateSingle(title, SIZE_TITLE, LIMIT_M);<\/p>\n<p>    var titleV;\n    if (!title) titleV = {level:'fail', word:'Fail', msg:'No title on the page.'};\n    else if (titleW > LIMIT_D) titleV = {level:'fail', word:'Fail', msg:'Wider than the \u2248600 px desktop cut-off, Google will likely truncate or rewrite it.'};\n    else if (titleLen > 60 || titleW > LIMIT_M) titleV = {level:'warn', word:'Warn', msg:'Fits desktop but is likely to be cut on mobile, or it is over the usual 60 character guideline.'};\n    else titleV = {level:'pass', word:'Pass', msg:'Fits both the \u2248600 px desktop and \u2248350 px mobile estimates.'};<\/p>\n<p>    var titleStats = titleLen + ' chars \\u00b7 \\u2248 ' + Math.round(titleW) + ' px wide';<\/p>\n<p>    var descV;\n    if (!desc) descV = {level:'fail', word:'Fail', msg:'No meta description: Google will compose its own snippet from the page content.'};\n    else if (descLen < 120) descV = {level:'warn', word:'Warn', msg:'Under 120 characters: you are leaving snippet space unused. Target the 120 to 160 zone.'};\n    else if (descLen > 160) descV = {level:'warn', word:'Warn', msg:'Over 160 characters: likely to be truncated in the results.'};\n    else descV = {level:'pass', word:'Pass', msg:'Inside the 120 to 160 character zone.'};<\/p>\n<p>    var hasT = false, hasD = false;\n    var kwV;\n    if (kw) {\n      var lkw = kw.toLowerCase();\n      hasT = title.toLowerCase().indexOf(lkw) !== -1;\n      hasD = desc.toLowerCase().indexOf(lkw) !== -1;\n      if (!hasT && !hasD) kwV = {level:'fail', word:'Miss', msg:'Keyword not found in the title or the meta description.'};\n      else if (!hasT) kwV = {level:'warn', word:'Warn', msg:'Keyword found in the description but missing from the title, where it matters most.'};\n      else if (!hasD) kwV = {level:'pass', word:'Pass', msg:'Keyword in the title; it is not in the description, which is fine if it cannot read naturally.'};\n      else kwV = {level:'pass', word:'Pass', msg:'Keyword present in both the title and the meta description.'};\n    } else {\n      kwV = {level:'info', word:'Off', msg:'Add a keyword to check where it appears.'};\n    }<\/p>\n<p>    \/\/ Verdict cards\n    var box = $('verdicts');\n    box.textContent = '';\n    function card(name, stats, verdict){\n      var d = document.createElement('div');\n      d.className = 'v ' + verdict.level;\n      var top = document.createElement('div');\n      top.className = 'vtop';\n      var n = document.createElement('span');\n      n.className = 'vname';\n      n.textContent = name;\n      top.appendChild(n);\n      top.appendChild(badge(verdict.level, verdict.word));\n      d.appendChild(top);\n      var s = document.createElement('div');\n      s.className = 'vstats';\n      s.textContent = stats;\n      d.appendChild(s);\n      if (verdict.msg) {\n        var m = document.createElement('div');\n        m.className = 'vmsg';\n        m.textContent = verdict.msg;\n        d.appendChild(m);\n      }\n      box.appendChild(d);\n    }\n    card('Title', titleStats, titleV);\n    card('Meta description', descLen + ' chars', descV);\n    card('Keyword', kw ? 'checking \"' + kw + '\"' : 'no keyword set', kwV);<\/p>\n<p>    \/\/ Keyword chips strip\n    var strip = $('kw');\n    strip.textContent = '';\n    function chip(label, cls){\n      var c = document.createElement('span');\n      c.className = 'chip ' + cls;\n      c.textContent = label;\n      strip.appendChild(c);\n    }\n    if (kw) {\n      chip('Keyword in title: ' + (hasT ? 'yes' : 'no'), hasT ? 'ok' : 'bad');\n      chip('Keyword in meta description: ' + (hasD ? 'yes' : 'no'), hasD ? 'ok' : 'soft');\n    } else {\n      chip('Add a keyword to run the keyword check.', '');\n    }<\/p>\n<p>    \/\/ SERP previews\n    var dispUrl = cleanUrl(url);\n    var descd = descShown(desc, SIZE_DESC_D, LIMIT_D, LINES_D);\n    var descm = descShown(desc, SIZE_DESC_M, LIMIT_M, LINES_M);\n    renderSerp($('pv-desktop'), dT.shown || 'Your page title', dispUrl, descd.shown);\n    renderSerp($('pv-mobile'), mT.shown || 'Your page title', dispUrl, descm.shown);<\/p>\n<p>    var nd = $('note-desktop');\n    nd.textContent = title ? ('Title: ' + titleLen + ' chars, \\u2248 ' + Math.round(titleW) + ' px, ' + (dT.truncated ? 'cut at ' + dT.visible + ' chars' : 'shows in full') + '. ' + (descd.truncated ? 'Snippet cut at ' + descd.visible + ' chars.' : 'Snippet: ' + descLen + ' chars, shows in full.')) : 'Nothing to preview yet.';\n    var nm = $('note-mobile');\n    nm.textContent = title ? ('Title: ' + (mT.truncated ? 'cut at ' + mT.visible + ' of ' + titleLen + ' chars' : 'shows in full (' + titleLen + ' chars)') + '. ' + (descm.truncated ? 'Snippet cut at ' + descm.visible + ' chars.' : 'Snippet: ' + descLen + ' chars, shows in full.')) : 'Nothing to preview yet.';<\/p>\n<p>    \/\/ stash state for copy\n    update.copyState = {title: title, desc: desc, kw: kw, dispUrl: dispUrl, tV: titleV, dV: descV, kT: hasT, kD: hasD};\n  }<\/p>\n<p>  function loadExample(){\n    $('i-title').value = 'SEO Title Length Checker: Free Tool with SERP Preview';\n    $('i-desc').value = 'Free SEO title length checker for meta descriptions: character counts, pixel width, truncation on desktop and mobile, and keyword flags, all in your browser.';\n    $('i-kw').value = 'seo title length checker';\n    $('i-url').value = 'https:\/\/seoedgeai.com\/blog\/meta-title-description-checker\/';\n    update();\n  }<\/p>\n<p>  function copyPreview(){\n    var st = update.copyState;\n    if (!st) return;\n    var lines = [];\n    lines.push('Meta title (' + st.title.length + ' chars, ' + st.tV.word + '): ' + st.title);\n    lines.push('');\n    lines.push('Meta description (' + st.desc.length + ' chars, ' + st.dV.word + '): ' + st.desc);\n    lines.push('');\n    lines.push('Display URL: ' + st.dispUrl);\n    if (st.kw) lines.push('Keyword \"' + st.kw + '\": ' + (st.kT ? 'in title' : 'not in title') + ', ' + (st.kD ? 'in description' : 'not in description') + '.');\n    lines.push('');\n    lines.push('Checked with the SEOEdgeAI meta title and description checker.');\n    var text = lines.join('\\n');\n    function done(){ var b = $('btn-copy'); b.textContent = 'Copied'; setTimeout(function(){ b.textContent = 'Copy preview block'; }, 1600); }\n    if (navigator.clipboard && navigator.clipboard.writeText) {\n      navigator.clipboard.writeText(text).then(done, function(){\n        legacyCopy(text); done();\n      });\n    } else {\n      legacyCopy(text); done();\n    }\n  }\n  function legacyCopy(text){\n    try {\n      var ta = document.createElement('textarea');\n      ta.value = text;\n      ta.style.position = 'fixed';\n      ta.style.opacity = '0';\n      document.body.appendChild(ta);\n      ta.select();\n      document.execCommand('copy');\n      document.body.removeChild(ta);\n    } catch(e) {}\n  }<\/p>\n<p>  $('btn-example').addEventListener('click', loadExample);\n  $('btn-copy').addEventListener('click', copyPreview);\n  $('f').addEventListener('submit', function(e){ e.preventDefault(); });\n  ['i-title','i-desc','i-kw','i-url'].forEach(function(id){\n    $(id).addEventListener('input', update);\n  });\n  update();\n})();\n<\/script><br \/>\n<\/body><br \/>\n<\/html><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Check your SEO title length and meta description in a free SERP preview: character counts, estimated pixel width, desktop and mobile truncation, and keyword flags. It runs in your browser, no sign-up needed.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"page-tool.php","meta":{"footnotes":""},"class_list":["post-129","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/seoedgeai.com\/blog\/wp-json\/wp\/v2\/pages\/129","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/seoedgeai.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/seoedgeai.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/seoedgeai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/seoedgeai.com\/blog\/wp-json\/wp\/v2\/comments?post=129"}],"version-history":[{"count":1,"href":"https:\/\/seoedgeai.com\/blog\/wp-json\/wp\/v2\/pages\/129\/revisions"}],"predecessor-version":[{"id":131,"href":"https:\/\/seoedgeai.com\/blog\/wp-json\/wp\/v2\/pages\/129\/revisions\/131"}],"wp:attachment":[{"href":"https:\/\/seoedgeai.com\/blog\/wp-json\/wp\/v2\/media?parent=129"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}