পাতা সাজানো হচ্ছে•••
বিজ্ঞাপনের স্থানহেডারের নিচে
পড়ার সময়মন্তব্যে যান

Tuesday, October 20, 2026

HTML ও CSS দিয়ে Contact Form: JavaScript ভ্যালিডেশনসহ বাংলা গাইড

HTML ও CSS ফর্ম—ইনপুট ও যাচাই চিহ্নের মৌলিক চিত্র

এই লেখার কোড একটি শিক্ষামূলক, front-end-only ডেমো। এটি কোনো বার্তা পাঠায় না, কোনো API-তে request করে না এবং কোনো backend submit নেই। Submit চাপলে সফল দেখালেও স্পষ্টভাবে “NOT sent” লেখা হয়—তাই এটিকে production contact system মনে করবেন না।

কেন contact form-এ শুধু সুন্দর input যথেষ্ট নয়

Contact form দেখতে ছোট হলেও এটি ব্যবহারকারীর ভাষা, browser-এর native validation, keyboard navigation, assistive technology এবং ভবিষ্যৎ server নিরাপত্তার মাঝখানে একটি চুক্তি তৈরি করে। একটি input-এর পাশে placeholder বসালেই সেই চুক্তি পূর্ণ হয় না। Placeholder ফাঁকা হলে label হারিয়ে যায়; লেবেল না থাকলে screen reader ব্যবহারকারী কোন তথ্য চাইছে বুঝতে পারেন না; error কেবল লাল border হলে রঙ না দেখা বা কম contrast-এর ব্যবহারকারী কারণটি জানতে পারেন না। তাই এখানে লক্ষ্য হল—একই markup থেকে আগে HTML-এর স্বাভাবিক সুবিধা পাওয়া, পরে অল্প JavaScript দিয়ে বার্তা ও interaction উন্নত করা।

এই article-এ আমরা নাম, email, বিষয় এবং বার্তার চারটি field দিয়ে একটি ছোট contact form বানাব। প্রতিটি field-এর দৃশ্যমান label থাকবে, required এবং type="email" browser-কে প্রাথমিক constraint দেবে, CSS :focus-visible keyboard focus আলাদা করে দেখাবে, আর aria-live status ফলাফল জানাবে। JavaScript প্রথমে native validity-কে সম্মান করবে; invalid হলে browser-এর reportValidity() চালিয়ে প্রথম ভুল field-এ মনোযোগ দেবে। সব ঠিক থাকলে কোনো network call নয়, কেবল demonstration message দেখাবে। এই progressive নকশায় HTML constraints (required, email ও minlength) native validation-এর নিয়ম হিসেবে থাকে। তবে এই local-only demo-তে submit button JavaScript চালু না হওয়া পর্যন্ত disabled থাকে; তাই JavaScript বন্ধ থাকলে কোনো submission বা success message হয় না, এবং কোনো বার্তাও পাঠানো হয় না।

প্রথম সিদ্ধান্ত: form-এর semantic চুক্তি

<form> element ব্যবহারকারীর interactive controls-এর একটি document section। বাস্তব form-এ action ও method দিয়ে endpoint ও HTTP আচরণ নির্ধারিত হয়। এই শিক্ষামূলক demo-তে method="dialog" রাখা হয়েছে, কিন্তু form-টির কোনো <dialog> ancestor নেই; তাই এটি dialog close করার form নয় এবং browser-এর কোনো form network submissionও হবে না। JavaScript listener validation ও demo status চালায়, কিন্তু no-network নিশ্চয়তার একমাত্র ভিত্তি preventDefault() নয়। ভবিষ্যতে backend যোগ করার সময় এই demo-র method, message ও privacy notice নতুন করে লিখতে হবে।

প্রতিটি label-এর for value তার input-এর id-এর সঙ্গে মেলে। এতে label-এ click করলে field focus হয় এবং screen reader relationship বুঝতে পারে। name attribute বাদ দিইনি: আজ request না গেলেও বাস্তব backend-এ কোন key পাঠানো হবে সেটি name নির্ধারণ করে। Name, email ও message-এ autocomplete-এর অর্থপূর্ণ মান দিয়েছি; subject-এর জন্য off বা custom value ব্যবহারের বদলে browser-কে অপ্রয়োজনীয় autofill না করতে বলেছি। aria-describedby field-কে ছোট hint এবং error-এর সঙ্গে যুক্ত করবে, যদিও error element শুরুতে hidden।

দেখুন MDN-এর form element reference। সেখানে action, method, enctype, novalidate এবং form-এর native আচরণ সম্পর্কে browser-সম্মত ব্যাখ্যা আছে। এই article-এ novalidate ব্যবহার করা হয়নি, কারণ native constraint validation-ই আমাদের প্রথম প্রতিরক্ষা।

Field বাছাই এবং constraint-এর যুক্তি

নামের ক্ষেত্রে minlength="2" দিয়েছি—এক অক্ষরের নাম সবসময় ভুল নয়, কিন্তু এই sample-এর product rule হিসেবে দুই অক্ষর চেয়েছি। Email input-এ type="email" browser-কে email syntax যাচাই করতে সাহায্য করে, আর maxlength অস্বাভাবিক দীর্ঘ input কমায়। Subject-এ required এবং সীমিত maxlength; বার্তার textarea-তে minlength="20" রেখেছি যাতে “হাই” ধরনের অস্পষ্ট request না যায়। এটি কোনো universal truth নয়—বাস্তব product-এর ভাষা, ব্যবহারকারী এবং business rule অনুযায়ী সীমা বদলাবেন।

required শুধু UI সাজানোর চিহ্ন নয়; browser validity state-এ valueMissing সেট করে। type=email ভুল syntax হলে typeMismatch তৈরি করে। minlength ও maxlength client-side feedback উন্নত করে, কিন্তু malicious বা hand-crafted HTTP request আটকায় না। একজন ব্যবহারকারী DevTools দিয়ে HTML বদলাতে পারেন, কিংবা form বাদ দিয়ে সরাসরি request পাঠাতে পারেন। সেই কারণে client constraint-কে সুবিধা হিসেবে দেখুন, security boundary হিসেবে নয়।

Error-এর জন্য আমি প্রতিটি field-এর নিচে একটি <p> রেখেছি। JavaScript error string লিখবে এবং hidden খুলবে; aria-describedby আগে থেকেই সেই node-কে সম্পর্কিত করে। role="alert" সব error-এ দিলে typing-এর সময় অতিরিক্ত interruption হতে পারে, তাই field error-কে polite live region এবং আলাদা global status-কে role="status" রাখা হয়েছে। Submit ব্যর্থ হলে browser-এর native popup এবং দৃশ্যমান error—দুটির মধ্যে বিরোধ না তৈরি করতে প্রথমে reportValidity() ব্যবহার করেছি, তারপর custom message কেবল প্রয়োজনীয় ক্ষেত্রে আপডেট করেছি।

CSS: focus এবং error-কে শুধু রঙে আটকে না রাখা

ফর্মের visual hierarchy ইচ্ছাকৃতভাবে ছোট: card, label, input, hint, error, action। এখানে কোনো generic responsive design tutorial নেই; CSS-এর কেন্দ্র field state। :focus-visible কেবল keyboard বা modality-তে দরকার হলে outline দেখায়, তাই mouse user-এর অপ্রত্যাশিত ring-এর বদলে keyboard user স্পষ্ট indication পান। Outline সরিয়ে ফেললে তা অবশ্যই নিজস্ব, দৃশ্যমান alternative দিয়ে পূরণ করতে হবে—এখানে outline বজায় রেখেছি।

:user-invalid সব browser-এ একইভাবে পুরোনো নয়, তাই demo-তে JavaScript-এর .has-error class-এর সঙ্গে :focus এবং :required ব্যবহার করেছি। Error field-এ border color বদলেছে, কিন্তু পাশে text message এবং icon-এর বদলে “সমস্যা” শব্দও আছে—এতে color-only dependency কমে। High-contrast mode বা forced-colors-এ user-agent-এর system color সম্মান করতে @media (forced-colors: active)-এ CanvasText ও Highlight ব্যবহার করেছি। Reduced-motion preference-এ animation নেই; কারণ validation message-এর জন্য animation কোনো দরকারি তথ্য যোগ করে না।

Progressive JavaScript flow

Script-এর flow চার ধাপে পড়ুন। প্রথমে form ও field references নেওয়া হয়। দ্বিতীয়ত, showFieldError() value, validity এবং custom message দেখে text বসায়। তৃতীয়ত, input event-এ user আবার লেখা শুরু করলে stale error সরানো হয়; এতে আগের ভুল বার্তা আটকে থাকে না। চতুর্থত, submit handler form.checkValidity() দিয়ে সামগ্রিক ফল পায়। false হলে form.reportValidity() browser-কে native message দেখাতে দেয়, তারপর প্রথম invalid element-এ focus() করা হয়। এই call-গুলো Constraint Validation API-এর অংশ; বিস্তারিত MDN Constraint validation guide পড়ুন।

সব field valid হলে handler event.preventDefault()-এর পরও কোনো fetch, XMLHttpRequest, form submit বা storage API কল করে না। status.textContent-এ লেখা হয়: “Demo success: validation passed, but message NOT sent.” এই বাক্যটি ইচ্ছাকৃতভাবে success শব্দ এবং non-delivery দুটোই বলে। Form reset না করায় learner চাইলে input দেখে DOM inspect করতে পারেন; reset করলে value হারানোর ঝুঁকি থাকে। বাস্তব app-এ server response আসা পর্যন্ত button disabled, idempotency এবং retry policy লাগতে পারে—এই demo সেগুলোর ভান করে না।

Custom validity কখন ব্যবহার করবেন? দুই field-এর সম্পর্ক যাচাই করতে setCustomValidity() ভালো উপায়, যেমন confirm email বা date range। কিন্তু এই sample-এ প্রতিটি field-এর rule native attribute-এই যথেষ্ট, তাই কৃত্রিম custom validity বসাইনি। এতে একটি গুরুত্বপূর্ণ শিক্ষা থাকে: JavaScript দিয়ে browser-এর built-in constraint আবার কপি না করে, native rule-কে feedback layer দিয়ে সমর্থন করুন। একই সঙ্গে validation message localization, browser-specific wording এবং accessible explanation-এর ভারসাম্য রাখুন।

সম্পূর্ণ standalone index.html

নিচের একটিমাত্র file save করে সরাসরি browser-এ খুলুন। কোনো package, build step বা external JavaScript দরকার নেই। Internet ছাড়াও demo চলবে, কারণ CSS ও script inline। method="dialog" এখানে কেবল demo-র জন্য; কোনো <dialog> ancestor না থাকায় form কোনো network submission করবে না। JavaScript চালু হলে তবেই demo button সক্রিয় হয়।

<!doctype html>
<html lang="bn">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Accessible Contact Form Demo</title>
  <style>
    :root { color-scheme: light; font-family: system-ui, sans-serif; line-height: 1.5; }
    * { box-sizing: border-box; }
    body { margin: 0; background: #eef2ff; color: #172033; }
    main { width: min(100% - 2rem, 42rem); margin: 4rem auto; }
    .card { background: #fff; padding: clamp(1.25rem, 4vw, 2.5rem); border: 1px solid #cbd5e1; border-radius: 1rem; box-shadow: 0 1rem 2rem #17203318; }
    h1 { margin-top: 0; }
    .notice { padding: .8rem 1rem; border-left: .3rem solid #7c3aed; background: #f5f3ff; }
    .field { margin-block: 1.25rem; }
    label { display: block; font-weight: 700; margin-bottom: .35rem; }
    input, textarea { width: 100%; border: 2px solid #94a3b8; border-radius: .5rem; padding: .7rem .8rem; font: inherit; color: inherit; background: #fff; }
    textarea { min-height: 9rem; resize: vertical; }
    input:focus-visible, textarea:focus-visible, button:focus-visible { outline: 3px solid #f59e0b; outline-offset: 3px; }
    input:invalid:not(:placeholder-shown), textarea:invalid:not(:placeholder-shown), .has-error { border-color: #b42318; }
    .hint, .error { font-size: .9rem; margin: .35rem 0 0; }
    .hint { color: #475569; }
    .error { color: #b42318; font-weight: 650; }
    button { border: 0; border-radius: .5rem; padding: .75rem 1.1rem; font: inherit; font-weight: 700; cursor: pointer; background: #3730a3; color: #fff; }
    button:hover { background: #312e81; }
    #status { min-height: 1.6rem; margin-top: 1rem; font-weight: 700; }
    .success { color: #166534; }
    @media (forced-colors: active) { input, textarea { border: 2px solid CanvasText; } input:focus-visible, textarea:focus-visible, button:focus-visible { outline-color: Highlight; } }
  </style>
</head>
<body>
  <main>
    <section class="card" aria-labelledby="form-title">
      <h1 id="form-title">যোগাযোগের ডেমো</h1>
      <p class="notice">এটি শেখার জন্য front-end demo। Submit করলে কোনো বার্তা পাঠানো হবে না।</p>
      <form id="contact-form" method="dialog">
        <div class="field">
          <label for="name">নাম <span aria-hidden="true">*</span></label>
          <input id="name" name="name" type="text" autocomplete="name" minlength="2" maxlength="80" required aria-describedby="name-hint name-error">
          <p id="name-hint" class="hint">কমপক্ষে ২ অক্ষর লিখুন।</p>
          <p id="name-error" class="error" hidden></p>
        </div>
        <div class="field">
          <label for="email">ইমেইল <span aria-hidden="true">*</span></label>
          <input id="email" name="email" type="email" autocomplete="email" maxlength="254" required aria-describedby="email-hint email-error">
          <p id="email-hint" class="hint">উত্তরের জন্য একটি বৈধ email দিন।</p>
          <p id="email-error" class="error" hidden></p>
        </div>
        <div class="field">
          <label for="subject">বিষয় <span aria-hidden="true">*</span></label>
          <input id="subject" name="subject" type="text" maxlength="120" required aria-describedby="subject-hint subject-error">
          <p id="subject-hint" class="hint">আপনার প্রশ্নের সংক্ষিপ্ত শিরোনাম।</p>
          <p id="subject-error" class="error" hidden></p>
        </div>
        <div class="field">
          <label for="message">বার্তা <span aria-hidden="true">*</span></label>
          <textarea id="message" name="message" minlength="20" maxlength="2000" required aria-describedby="message-hint message-error"></textarea>
          <p id="message-hint" class="hint">কমপক্ষে ২০ অক্ষরে সমস্যাটি লিখুন।</p>
          <p id="message-error" class="error" hidden></p>
        </div>
        <button id="submit-button" type="submit" disabled>ডেমো যাচাই করুন</button>
        <p id="status" role="status" aria-live="polite"></p>
      </form>
    </section>
  </main>
  <script>
    const form = document.querySelector('#contact-form');
    const fields = [...form.querySelectorAll('input, textarea')];
    const status = document.querySelector('#status');
    const submitButton = document.querySelector('#submit-button');

    function messageFor(field) {
      if (field.validity.valueMissing) return 'এই ঘরটি পূরণ করুন।';
      if (field.validity.typeMismatch) return 'একটি বৈধ email format দিন।';
      if (field.validity.tooShort) return `কমপক্ষে ${field.minLength} অক্ষর লিখুন।`;
      if (field.validity.tooLong) return `সর্বোচ্চ ${field.maxLength} অক্ষর রাখা যাবে।`;
      return '';
    }

    function showFieldError(field) {
      const error = document.querySelector(`#${field.id}-error`);
      const text = messageFor(field);
      error.textContent = text;
      error.hidden = !text;
      field.classList.toggle('has-error', Boolean(text));
      field.setAttribute('aria-invalid', text ? 'true' : 'false');
      return !text;
    }

    fields.forEach((field) => {
      field.addEventListener('input', () => {
        showFieldError(field);
        if (fields.every((item) => item.validity.valid)) status.textContent = '';
      });
    });

    form.addEventListener('submit', (event) => {
      event.preventDefault(); // Demo: deliberately prevents any network submission.
      status.className = '';
      const valid = form.checkValidity();
      fields.forEach(showFieldError);
      if (!valid) {
        form.reportValidity();
        const firstInvalid = fields.find((field) => !field.validity.valid);
        firstInvalid?.focus();
        status.textContent = 'কিছু ঘর ঠিক করুন, তারপর আবার চেষ্টা করুন।';
        return;
      }
      status.className = 'success';
      status.textContent = 'Demo success: validation passed, but message NOT sent.';
    });

    // JavaScript is required for this local-only demonstration button.
    submitButton.disabled = false;
  </script>
</body>
</html>

কোড পড়ার সময় যে ছোট সিদ্ধান্তগুলো খেয়াল করবেন

aria-invalid="false" শুরুতেই লেখা নেই; user প্রথম submit বা input করার পর script সেট করে। এতে untouched field-এ শুরুতেই error state ঘোষণা হয় না। Error paragraph-এর hidden attribute screen reader এবং visual user—দুজনের জন্যই শুরুতে content সরিয়ে রাখে। Text বদলানোর পর aria-describedby-এর সম্পর্ক একই থাকে, তাই error প্রকাশিত হলে assistive technology সেটি পড়তে পারে। role="status" polite হওয়ায় typing-এর মাঝখানে জরুরি alert-এর মতো interrupt করবে না। Submit-এর পরে browser native bubble দেখালেও আমাদের status-এ সংক্ষিপ্ত নির্দেশ থাকে, ফলে keyboard user বুঝতে পারেন যে কী করবেন।

input listener-এ আমরা custom regular expression দিয়ে email বানাইনি। Email format-এর জন্য browser-এর type=email যথেষ্ট প্রাথমিক rule; messageFor()-এর typeMismatch message কেবল বাংলা feedback। Browser-এর validity property-কে source of truth রাখায় rule এবং message আলাদা হয়ে অসঙ্গত হওয়ার সম্ভাবনা কম। maxlength user input-এ browser সাধারণত সীমা মানে, কিন্তু server-এ একই সীমা আবার enforce করতে হবে। কোনো field-এ whitespace-only policy দরকার হলে backend-এ trim ও business validation করুন; এই demo সেই policy গোপনে ধরে নেয় না।

Validation test cases: ইচ্ছাকৃতভাবে ভাঙুন

প্রথম test-এ সব ঘর খালি রেখে button চাপুন। Browser প্রথম required field দেখাবে, script সব error paragraph প্রকাশ করবে এবং status বলবে কিছু ঘর ঠিক করতে। এরপর name-এ এক অক্ষর দিন; tooShort message দেখা উচিত। দুই বা তার বেশি অক্ষর দিলে সেই error অদৃশ্য হবে। এই sequence-টি নিশ্চিত করে যে invalid state আটকে নেই এবং user input-এ feedback আপডেট হচ্ছে।

দ্বিতীয় test-এ email-এ hello লিখুন। Browser typeMismatch জানাবে এবং বাংলা message “বৈধ email format” দেখাবে। person@example.com দিলে email error সরে যাওয়ার কথা। তৃতীয় test-এ subject ফাঁকা রাখুন, message-এ ১৯টি অক্ষর দিন; message-এর minimum constraint আলাদা করে কাজ করবে। অনেক বড় text paste করে maxlength-এর আচরণও দেখুন।

চতুর্থ test হল keyboard-only: mouse না নিয়ে Tab দিয়ে label-এর পর field-এ যান, Enter/Space-এ submit করুন। কমলা focus-visible outline দেখা উচিত এবং invalid হলে প্রথম ভুল field focus পাওয়া উচিত। পঞ্চম test-এ browser DevTools থেকে JavaScript নিষ্ক্রিয় করে page reload করুন। Script-এর বাংলা custom error ও demo success থাকবে না, কিন্তু required, email, minlength-এর native validation চলবে। এই fallback-এ browser-এর native constraints দেখা যায়, কিন্তু demo button ইচ্ছাকৃতভাবে disabled থাকায় JavaScript ছাড়া submission শুরু হয় না; ফলে network-এ কিছু পাঠানোর দাবিও নেই।

ষষ্ঠ test-এ screen reader বা browser accessibility tree দিয়ে status ও error পড়ে দেখুন। Error text কেবল color নয় এবং label/hint/error সম্পর্কিত কিনা যাচাই করুন। সপ্তম test-এ forced-colors mode এবং zoom 200% চালু করুন; content কাটা বা focus হারানো উচিত নয়। সব test সফল হলেও এটি security test নয়—network, authorization, abuse prevention ও data retention আলাদা যাচাই।

Production-এ গেলে server validation অপরিহার্য

এই page-এ client-side validation user experience-এর জন্য, বিশ্বাসের জন্য নয়। Production endpoint-এ server অবশ্যই request body parse করে নাম, email, subject ও message-এর type, length, encoding এবং business rule যাচাই করবে। Unknown fields বাদ দিন, canonicalize করার সময় data নষ্ট না করে নীতিমালা লিখুন, এবং error response-এ sensitive details ফাঁস করবেন না। Server-side validation client rule-এর সঙ্গে সামঞ্জস্যপূর্ণ হতে পারে, কিন্তু client code কপি করে নিরাপত্তা সম্পন্ন হয়েছে ভাবা যাবে না।

বাস্তব POST form-এ CSRF প্রতিরোধ আলাদা বিষয়। Cookie-based session থাকলে server-side CSRF token তৈরি করে form-এ যুক্ত করুন এবং server-এ token মিলিয়ে দেখুন; SameSite cookie, Origin/Referer যাচাই এবং framework-এর vetted CSRF middleware সহায়ক প্রতিরক্ষা। এই demo-তে token নেই, কারণ backend নেই—তাই এটিকে authenticated production form হিসেবে ব্যবহার করবেন না। এই demo-র method="dialog" কোনো CSRF protection নয়; বাস্তব endpoint যোগ করলে action, method, CSRF ও privacy policy নতুন করে নির্ধারণ করতে হবে।

Contact form সাধারণত spam target। Rate limit, IP বা account ভিত্তিক quota, exponential backoff, captcha/turnstile-এর মতো risk-based challenge এবং abuse monitoring দরকার হতে পারে। Rate limit server বা edge-এ প্রয়োগ করুন; JavaScript-এ button একবার disable করলেই attacker থামে না। Message email-এ forward করলে header injection, HTML escaping, attachment policy এবং outbound provider-এর failure handling ভাবুন। সফল response তখনই বলবেন যখন server সত্যিই গ্রহণ করেছে; এই demo-র “NOT sent” ভাষা সেই distinction শেখায়।

Privacy, retention এবং error আচরণ

Form-এ email ও message ব্যক্তিগত তথ্য হতে পারে। Production-এ কেন তথ্য নিচ্ছেন, কতদিন রাখবেন, কে দেখবে, delete/access request কীভাবে হবে—সংক্ষিপ্ত privacy notice-এ বলুন। Consent checkbox দরকার হলে তা optional marketing consent-এর সঙ্গে বাধ্যতামূলক contact request মেশাবেন না। TLS ছাড়া sensitive form পাঠাবেন না; logs-এ সম্পূর্ণ message বা email না রাখাই ভালো, আর analytics tool যেন field value capture না করে তা যাচাই করুন।

Error response-এ “email already exists” বা account enumeration-এর মতো তথ্য প্রকাশের আগে threat model করুন। Accessibility-এর জন্য error message স্পষ্ট, সংক্ষিপ্ত এবং করণীয়মুখী রাখুন; “invalid” বলার বদলে “একটি বৈধ email format দিন” ভালো। Server 400, 429 বা 500 দিলে UI আলাদা state দেখাবে—network failure-কে validation error বলবে না। Retry-এর আগে user-এর entered value অক্ষুণ্ণ রাখুন, তবে shared/public device-এ sensitive data ধরে রাখার trade-off ব্যাখ্যা করুন।

Demo থেকে বাস্তব integration-এর checklist

  1. action-এ HTTPS endpoint দিন এবং method="post" রাখুন; GET-এ message পাঠাবেন না। ২. Server-side schema validation, CSRF token, authentication/authorization এবং rate limit যোগ করুন। ৩. Success message server-এর authoritative response থেকে বানান, front-end অনুমান থেকে নয়। ৪. Error payload-এ field-level safe messages দিন এবং unknown exception user-কে দেখাবেন না। ৫. Privacy notice, retention deletion এবং log redaction লিখিত policy-তে আনুন। ৬. Keyboard, screen reader, zoom, forced-colors, slow network এবং JavaScript-off test চালান। ৭. fetch ব্যবহার করলে timeout, abort, retry policy ও duplicate submission সামলান—এই file-এ সেগুলো ইচ্ছাকৃতভাবে নেই।

শেষ কথা: একটি ভালো form-এর মূল্য submit button-এর gradient-এ নয়; user কী চাইছে বুঝতে পারছেন কিনা, ভুল হলে কীভাবে ফেরত আসবেন, keyboard-এ সব reachable কিনা, এবং data কোথায় যাচ্ছে তা সত্যি বলা হচ্ছে কিনা—এসবের সমষ্টিতে। এই standalone demo native HTML দিয়ে শুরু করে CSS focus feedback ও JavaScript status যোগ করেছে, কিন্তু কোনো fake backend বা misleading success বানায়নি। সেই সীমা বজায় রেখেই পরের ধাপে নিজের server contract যোগ করুন।

রেফারেন্স

শেয়ার করুন WhatsApp Facebook Telegram X

No comments:

Post a Comment

বিজ্ঞাপনের স্থানপোস্ট ১
বিজ্ঞাপনের স্থানপোস্ট ২
বিজ্ঞাপনের স্থানপোস্ট ৩
About Me
পরিচয়
নাম

নাজমুল ইসলাম

ভূমিকা

Tech Specialist

আরও পরিচিতি
শিক্ষা / পড়াশোনার বিষয়
HSC
কাজ / প্রযুক্তির ক্ষেত্র / দক্ষতা
শহর / জেলা
শখ / আগ্রহ
ব্লগের বিষয় / পাঠকদের জন্য সংক্ষিপ্ত পরিচিতি
ব্যক্তিগত লেখা, ছোট গল্প ও প্রযুক্তি ভাবনা নিয়ে লিখি।
পাবলিক ওয়েবসাইট / সোশ্যাল লিংক
শেষের পরিচিতি
নাজমুল ইসলাম · Tech Specialist

লেখার আর্কাইভ

মাস অনুযায়ী পুরোনো লেখাগুলো খুঁজে নিন।