इस पेज का अनुवाद मशीन द्वारा किया गया है और इसमें त्रुटियाँ हो सकती हैं। मूल अंग्रेज़ी पढ़ें

दस्तावेज़

Tailwind

NSH की स्टाइलिंग Tailwind CSS 4 का पालन करती है। Rust में, Tailwind की यूटिलिटीज़ उन्हीं नामों और मानों वाले टाइप्ड स्टाइल मेथड हैं (.p_4() है p-4, .rounded_2xl() है rounded-2xl, ZINC.shade(900) है zinc-900); TSX में, हर एलिमेंट class में सीधे क्लासेज़ स्वीकार करता है, जिन्हें Tailwind 4 की डिफ़ॉल्ट थीम के विरुद्ध नेटिव रूप से रिज़ॉल्व किया जाता है। नीचे हर उदाहरण में पहले टाइप्ड मेथड और फिर क्लासेज़ दिखाई गई हैं:

guide-tailwind.tsx
guide-tailwind.tsxLive
import { mount, Scroll, signal, Text, View } from "nsh";

function App() {
  const count = signal(0);
  const busy = signal(false);
  const tab = signal("Day");
  const tabs = ["Day", "Week", "Month"];

  const buttons = () => (
    <View class="flex-row flex-wrap gap-2">
      <View
        onPress={() => count.update((n) => n + 1)}
        class="rounded-lg bg-lime-300 px-4 py-2 transition-colors duration-150 motion-reduce:transition-none hover:bg-lime-200 active:bg-lime-400 focus-visible:outline-2 focus-visible:outline-sky-500"
      >
        <Text class="font-medium text-lime-950">Pressed {count()}×</Text>
      </View>
      <View
        onPress={() => busy.set(!busy())}
        class="rounded-lg border border-black/10 bg-white px-4 py-2 shadow-sm transition motion-reduce:transition-none hover:bg-zinc-50 active:scale-95 active:bg-zinc-100 dark:border-[#2c2e33] dark:bg-[#24262a] dark:hover:bg-[#2a2c31] dark:active:bg-[#2d2f34] focus-visible:outline-2 focus-visible:outline-sky-500"
      >
        <Text class="font-medium text-[#15161a] dark:text-[#eceef1]">{busy() ? "Enable" : "Disable"}</Text>
      </View>
      <View disabled={busy()} onPress={() => count.set(0)} class="rounded-lg bg-emerald-400 px-4 py-2 enabled:hover:bg-emerald-300 active:bg-emerald-500 disabled:opacity-40 focus-visible:outline-2 focus-visible:outline-sky-500">
        <Text class="font-medium text-emerald-950">Reset</Text>
      </View>
    </View>
  );
  const tabsRow = () => (
    <View class="flex-row rounded-xl border border-black/5 bg-[#ecece7] p-1 dark:border-[#1f2124] dark:bg-[#0c0d0f]">
      <Each of={tabs}>
        {(t) => (
          <View
            onPress={() => tab.set(t())}
            class="flex-1 items-center rounded-lg py-2 transition-colors motion-reduce:transition-none active:opacity-70 focus-visible:outline-2 focus-visible:outline-sky-500"
            classList={{ "bg-white shadow-sm dark:bg-[#2a2c31]": tab() === t(), "hover:bg-black/5 dark:hover:bg-white/5": tab() !== t() }}
          >
            <Text class="text-sm font-medium" classList={{ "text-[#15161a] dark:text-[#eceef1]": tab() === t(), "text-[#565a61] dark:text-[#8d9198]": tab() !== t() }}>
              {t()}
            </Text>
          </View>
        )}
      </Each>
    </View>
  );
  const gradient = () => (
    <View class="overflow-hidden rounded-2xl bg-linear-to-br from-lime-300 via-emerald-300 to-sky-400 p-5 shadow-xl shadow-lime-300/20">
      <Text class="text-xl font-bold text-zinc-950">{tab()} view</Text>
      <Text class="mt-1 text-sm text-zinc-950/70">bg-linear-to-br from-lime-300 via-emerald-300 to-sky-400</Text>
    </View>
  );
  const grid = () => (
    <View class="grid grid-cols-3 gap-2">
      <View class="col-span-2 h-14 items-center justify-center rounded-lg bg-sky-950">
        <Text class="text-xs text-sky-200">col-span-2</Text>
      </View>
      <View class="h-14 rounded-lg bg-sky-900" />
      <View class="h-14 rounded-lg bg-sky-800" />
      <View class="row-span-2 rounded-lg bg-sky-400" />
      <View class="h-14 rounded-lg bg-sky-700" />
      <View class="h-14 rounded-lg bg-sky-600" />
      <View class="h-14 rounded-lg bg-sky-500" />
    </View>
  );
  const list = () => (
    <View class="overflow-hidden rounded-xl border border-black/10 bg-white shadow-sm dark:border-[#2c2e33] dark:bg-[#18191c]">
      <Each of={["Inbox", "Drafts", "Sent", "Archive"]}>
        {(item) => (
          <View class="flex-row items-center justify-between border-t border-black/5 px-4 py-3 first:border-t-0 even:bg-[#fafaf7] active:bg-lime-50 dark:border-[#1f2124] dark:even:bg-[#141518] dark:active:bg-[#24262a]">
            <Text class="text-[#15161a] dark:text-[#eceef1]">{item()}</Text>
            <Text class="text-[#8c9097] dark:text-[#5a5e65]">›</Text>
          </View>
        )}
      </Each>
    </View>
  );
  const motion = () => (
    <View class="flex-row items-center justify-around py-2">
      <View class="size-8 animate-spin motion-reduce:animate-none rounded-md bg-lime-300" />
      <View class="size-8 animate-bounce motion-reduce:animate-none rounded-full bg-sky-400" />
      <View class="h-8 w-14 animate-pulse motion-reduce:animate-none rounded-md bg-[#e1e1db] dark:bg-[#2c2e33]" />
    </View>
  );

  return (
    <Scroll class="flex-1 bg-[#f3f3ef] dark:bg-[#101113]">
      <View class="gap-5 p-5 pt-6">
        <Text class="text-2xl font-bold tracking-tight text-[#15161a] dark:text-[#eceef1]">Tailwind classes</Text>
        {/* Two columns from `md:` on (768 points): breakpoints follow the window. */}
        <View class="gap-5 md:flex-row md:items-start">
          <View class="gap-5 md:min-w-0 md:flex-1">
            {buttons()}
            {tabsRow()}
            {gradient()}
          </View>
          <View class="gap-5 md:min-w-0 md:flex-1">
            {grid()}
            {list()}
            {motion()}
          </View>
        </View>
      </View>
    </Scroll>
  );
}

mount(App);
use nsh_core::element::palette::ZINC;

view()
    .flex_row()
    .items_center()
    .gap_3()
    .rounded_2xl()
    .bg(AdaptiveColor::new(white(), ZINC.shade(800)))
    .p_4()
    .shadow_lg()
    .active(|s| s.scale(0.98))
    .child(
        text("Tailwind on native views")
            .text_base()
            .font_weight(FontWeight::SEMIBOLD)
            .text_color(AdaptiveColor::new(ZINC.shade(900), white())),
    )

टाइप्ड मेथड उन्हीं स्टाइल नियमों में लोअर होते हैं जिनमें क्लासेज़ कंपाइल होती हैं, और पार्स करने के लिए कोई स्ट्रिंग नहीं होती। क्लासेज़ के लिए कोई CSS जनरेट नहीं होता और JavaScript में कुछ नहीं चलता: स्ट्रिंग कोर को दी जाती है, जो हर अलग क्लास स्ट्रिंग को एक बार पार्स करता है (और कैश करता है), उसे स्टाइल नियमों में बदलता है और लागू करता है। जो वेरिएंट स्थिति पर निर्भर हैं (pressed, dark mode, विंडो की चौड़ाई), उन्हें स्थिति बदलने पर कोर फिर से लागू करता है। pressed और focused स्थितियाँ नेटिव रूप से ट्रैक होती हैं, इसलिए active: फ़ीडबैक (Rust में .active(..)) के लिए आपके कोड तक कोई राउंड ट्रिप नहीं होता।

क्लासेज़ और style prop एक जैसी नेटिव स्टाइल बनाते हैं और साथ में इस्तेमाल किए जा सकते हैं; यह पेज केवल क्लासेज़ और उनके टाइप्ड समकक्षों की विशिष्टताएँ बताता है। Tailwind रेफ़रेंस में समर्थित हर यूटिलिटी सूचीबद्ध है।

#class और classList

view()
    .rounded_lg()
    .px_4()
    .py_2()
    .transition_colors()
    .bg(move || if selected.get() { INDIGO.shade(600) } else { ZINC.shade(200) })
    .style_when(busy, |s| s.opacity(0.5))
  • class क्लासेज़ की एक स्ट्रिंग है। यह डायनामिक हो सकती है: class={`h-6 rounded bg-${color()}-500`} काम करती है, क्योंकि क्लासेज़ रन टाइम पर रिज़ॉल्व होती हैं।
  • classList क्लासेज़ को शर्तों से मैप करता है: जब तक किसी क्लास का मान truthy है, वह लागू रहती है। यह class के बाद लागू होता है।
  • Rust में, क्लोज़र या सिग्नल दिया गया मेथड उसका अनुसरण करता है (.bg(move || ..)), .style_when(condition, |s| ..) शर्त के बने रहने तक स्टाइल लागू करता है, और .when(condition, |v| ..) एलिमेंट बनते समय एक बार तय करता है।

टकराव कैसे सुलझते हैं:

  1. बाद की क्लासेज़ जीतती हैं उसी स्ट्रिंग की पहले वाली क्लासेज़ पर: "p-2 p-4" का अर्थ p-4 है। tailwind-merge की कोई ज़रूरत नहीं: कंपोनेंट वह class जोड़ सकता है जो उसका कॉलर देता है। Rust में भी बाद के मेथड जीतते हैं।
  2. वेरिएंट क्लासेज़ सादी क्लासेज़ के बाद लागू होती हैं, और स्ट्रिंग में उनकी स्थिति चाहे जो हो, वे Tailwind के वेरिएंट क्रम में लगती हैं (hover: active: से पहले, max-*: में सबसे चौड़ा पहले, ब्रेकपॉइंट बढ़ते क्रम में, dark: सबसे अंत में)। Tailwind की तरह ही, पहले क्लास का सबसे ऊँचा वेरिएंट तय करता है, फिर उसका अगला सबसे ऊँचा, और जिस क्लास में एक वेरिएंट अधिक हो वह बाद में आती है: dark:hover:bg-a dark:bg-b डार्क मोड में होवर होने पर bg-a है, और max-md:p-2 max-lg:p-4 md से नीचे p-2 है। CSS की तरह, odd:bg-a last:bg-b विषम अंतिम पंक्ति को bg-a देता है, क्योंकि Tailwind odd को last के बाद क्रमबद्ध करता है।
  3. style क्लासेज़ पर जीतता है। स्टाइल कुंजी अपने नाम की CSS प्रॉपर्टी होती है, वही जिसे क्लासेज़ सेट करती हैं, इसलिए वह केवल उसी प्रॉपर्टी को बदलती है: style={{ paddingLeft: 8 }} pl-4 को बदलता है और pr-4 को बनाए रखता है।
  4. ! किसी क्लास को important बनाता है: !p-0।

Rust कंपोनेंट (nsh/base के और ऐप के अपने crate के) TSX से भी class और classList स्वीकार करते हैं; आपकी क्लासेज़ कंपोनेंट की अपनी स्टाइल के बाद मर्ज होती हैं और बराबर रैंक पर जीतती हैं।

#Variants

वेरिएंटकब लागू होता हैRust
active:व्यू दबा हुआ है.active(|s| ..)
hover:पॉइंटर व्यू के ऊपर है (iPad और Mac के पॉइंटर).hover(|s| ..)
focus:, focus-visible:इनपुट पर फ़ोकस है.focus(|s| ..)
focus-within:व्यू या उसके किसी डिसेंडेंट पर फ़ोकस है (Overlay की सामग्री उस व्यू का डिसेंडेंट है जिसमें वह है).focus_within(..)
disabled:, enabled:एलिमेंट का disabled prop true / false है.disabled_style(|s| ..)
selected:एलिमेंट चयनित है (Button का selected, accessibilityState.selected).selected_style(..)
checked:एलिमेंट चेक्ड है (accessibilityState.checked है true; "mixed" चेक्ड नहीं है).checked_style(..)
indeterminate:एलिमेंट indeterminate है (accessibilityState.checked है "mixed").indeterminate_style(..)
aria-expanded:एलिमेंट विस्तारित है (accessibilityState.expanded है true): Popover या Menu का ट्रिगर, जब उसका पॉपअप खुला हो.expanded_style(..)
aria-pressed:एलिमेंट एक टॉगल बटन है जो ऑन है (accessibilityState.pressed है true; "mixed" दबा हुआ नहीं है)। यह active: नहीं है, जो चल रहा प्रेस है.aria_pressed(..), .pressed_style(..)
aria-busy:, aria-checked:, aria-selected:accessibilityState.busy, checked (true, "mixed" नहीं) या selected true है, जैसे Tailwind के aria-* वेरिएंट: pseudo-classes के बाद, उनके नामों के क्रम में
data-<name>:एलिमेंट में data-* एट्रिब्यूट name है, जैसे Tailwind के बूलियन data-* वेरिएंट (&[data-range-start]): कंपोनेंट का पार्ट अपनी स्थितियों के अलावा जो दिखाता है, जैसे कैलेंडर का रेंज की शुरुआत वाला दिन (data-range-start:)। aria-* वेरिएंट के बाद, उनके नामों के क्रम मेंInteractive::project_data(name, ..) कंपोनेंट के पार्ट पर एक सेट करता है
group-active:, group-hover:, group-focus:, group-focus-within:, group-disabled:, group-selected:, group-checked:, group-indeterminate:, group-aria-expanded:, group-aria-pressed:, group-aria-busy:, group-aria-checked:, group-aria-selected:, group-data-<name>:निकटतम पूर्वज, जिसमें group क्लास है, उस स्थिति में है, या उसमें वह data-* एट्रिब्यूट हैपूर्वज पर .group(); अंदर .group_active(..), .group_hover(..), .group_focus_within(..)
group-active/name: …यही, group/name से चिह्नित पूर्वज के लिए.group_named("name"); .group_active_named("name", ..), …
first:, last:, odd:, even:, only:एलिमेंट की अपने सिबलिंग्स के बीच अपनी स्थिति। किसी पंक्ति के लेबल को पंक्ति की स्थिति के अनुसार रंगने के लिए last:text-* पंक्ति पर लगाएँ: लेबल उसे इनहेरिट करता हैपंक्ति के इंडेक्स से सादा Rust (at.is_first(), at.is_last(), at.index() % 2)
placeholder:इनपुट के प्लेसहोल्डर को स्टाइल करता है (placeholder:text-zinc-400).placeholder_color(..)
selection:इनपुट या चयन-योग्य टेक्स्ट, और एलिमेंट के भीतर मौजूद ऐसे एलिमेंट्स, के सिलेक्शन हाइलाइट को रंगता है (selection:bg-violet-200).selection_color(..)
dark:सिस्टम अपीयरेंस डार्क हैएक AdaptiveColor, या .dark(|s| ..)
sm:, md:, lg:, xl:, 2xl:विंडो कम से कम 640, 768, 1024, 1280, 1536 पॉइंट चौड़ी है.style_when(|| window().width >= px(768.0), |s| ..)
max-sm:, max-md:, …विंडो ब्रेकपॉइंट से संकरी हैवही, < के साथ
min-[600px]:, max-[600px]:मनमाने ब्रेकपॉइंटवही
portrait:, landscape:विंडो का ओरिएंटेशनwindow().width > window().height
ios:, android:, web:प्लेटफ़ॉर्म.when(os() == Os::Ios, |v| ..)
rtl:, ltr:लेआउट दिशाव्यू पर .direction(LayoutDirection::..)
motion-safe:, motion-reduce:उपयोगकर्ता की reduce motion सेटिंगreduce_motion()

वेरिएंट आपस में जुड़ते हैं: dark:active:bg-zinc-700, md:landscape:flex-row। Rust में, .active(..) स्टाइल में AdaptiveColor का अर्थ dark:active: है।

view()
    .group()
    .flex_row()
    .items_center()
    .gap_3()
    .rounded_xl()
    .bg(white())
    .p_3()
    .active(|s| s.bg(ZINC.shade(50)))
    .child(text("Settings").flex_1().text_color(ZINC.shade(900)))
    .child(
        text("›")
            .text_color(ZINC.shade(300))
            .transition_transform()
            .group_active(|s| s.translate_x(px(4.0))),
    )

किसी भी एलिमेंट पर disabled (Rust में .disabled(..)) disabled: क्लासेज़ (.disabled_style(..)) लागू करता है और जो माउंट किया गया है उससे उसके प्रेस हैंडलर हटा देता है, इसलिए वह कोई प्रेस फ़ीडबैक भी नहीं दिखाता:

view()
    .disabled(saving)
    .on_press(save)
    .rounded_lg()
    .bg(EMERALD.shade(500))
    .px_4()
    .py_2()
    .active(|s| s.bg(EMERALD.shade(600)))
    .disabled_style(|s| s.opacity(0.4))
    .child(text("Save").font_weight(FontWeight::MEDIUM).text_color(white()))

ब्रेकपॉइंट विंडो (वह सतह जिस पर NSH रेंडर करता है) से तुलना करते हैं, डिवाइस से नहीं: iPad के स्प्लिट व्यू या आकार बदलने योग्य विंडो में वे विंडो की चौड़ाई का अनुसरण करते हैं, जैसा window() करता है।

#वेब पर Tailwind से क्या अलग है

  • इकाइयाँ पॉइंट हैं। p-4 (.p_4()) 16 पॉइंट है (1 स्पेसिंग स्टेप = 4pt, 1rem = 16pt)। px में मनमाने मान भी पॉइंट हैं: p-[13px] (.p(px(13.0))) 13pt है।
  • flex एक पंक्ति है, जैसा CSS में होता है: class="flex" अपने चाइल्ड को एक पंक्ति में रखता है, जब तक कोई दिशा क्लास कुछ और न कहे (स्ट्रिंग में कहीं भी: flex-col flex एक कॉलम है)। किसी स्टाइल का display: "flex" और Rust का .flex() भी इसी तरह पंक्तियाँ हैं, और जो व्यू कोई display सेट नहीं करता वह NSH का डिफ़ॉल्ट कॉलम रखता है। block और flow-root ब्लॉक लेआउट इस्तेमाल करते हैं, और inline तथा inline-block भी, जैसे ब्राउज़र उन्हें flex या grid पेरेंट में लेआउट करता है। Rust में .flex_row() और .flex_col() दिशा सेट करते हैं।
  • व्यू पर टेक्स्ट क्लासेज़ इनहेरिट होती हैं। text-*, font-*, leading-*, tracking-* और व्यू पर अन्य टेक्स्ट यूटिलिटीज़ (Rust में .text_color(..), .font_weight(..), …) उसके अंदर के टेक्स्ट और इनपुट एलिमेंट्स तक पहुँचती हैं, जैसे CSS इनहेरिटेंस करती है: <View class="text-white"> उसके लेबल सफ़ेद कर देता है।
  • हर व्यू अपना एक बॉक्स है। overflow-x-hidden और overflow-y-hidden दोनों अक्षों पर क्लिप करते हैं, जैसा overflow-hidden करता है (स्क्रॉलिंग scroll एलिमेंट का काम है); invisible व्यू और उसके अंदर की हर चीज़ को छिपा देता है; mix-blend-* व्यू को उसके पीछे बनी हर चीज़ के साथ ब्लेंड करता है; block बॉक्स अपने मार्जिन और अपने चाइल्ड के मार्जिन को अलग रखते हैं; absolute व्यू अपने निकटतम पोज़िशन्ड पूर्वज में रखा जाता है, जैसा CSS में होता है; fixed व्यू को उसके रूट व्यू में लेआउट करता है (अन्य एलिमेंट absolute की तरह रखे जाते हैं)।
  • टेक्स्ट अपनी व्हाइटस्पेस बनाए रखता है। स्ट्रिंग में स्पेस और लाइन ब्रेक जैसे लिखे हैं वैसे ही बनते हैं, और whitespace-* रैपिंग तय करता है; JSX कंपाइलर लिटरल JSX टेक्स्ट की व्हाइटस्पेस को संकुचित करता है, जैसा JSX करता है।
  • ट्रांज़िशन प्रॉपर्टी समूहों को एनिमेट करते हैं: transition रंग, opacity, शैडो और ट्रांसफ़ॉर्म को एनिमेट करता है, transition-all कॉर्नर रेडियस और बॉर्डर की चौड़ाई को भी; फ़िल्टर, बैकड्रॉप फ़िल्टर, ग्रेडिएंट और लेआउट अपने नए मान तुरंत ले लेते हैं।
  • focus-visible: है focus:: यह तब भी लागू होता है जब एलिमेंट पर फ़ोकस हो।
  • स्थिति के बैकग्राउंड ग्रेडिएंट के ऊपर दिखते हैं। CSS में ग्रेडिएंट background-color को ढक लेता है, इसलिए bg-linear-* सतह पर active:bg-* कभी दिखाई नहीं देता; NSH में active:, hover:, focus: या group-*: से आया बैकग्राउंड रंग उस स्थिति के बने रहने तक ग्रेडिएंट की जगह ले लेता है (जब तक स्थिति अपना ग्रेडिएंट सेट न करे, जैसे active:from-*)। activeStyle और अन्य स्थिति स्टाइल, तथा एनिमेटेड बैकग्राउंड रंग भी ऐसे ही हैं।
  • टेक्स्ट और टच सेटिंग्स स्टाइल कुंजियाँ हैं: line-clamp-* numberOfLines सेट करता है, text-ellipsis और text-clip textOverflow सेट करते हैं, truncate दोनों, select-* userSelect सेट करता है और pointer-events-* pointerEvents सेट करता है, इसलिए वे स्थिति वेरिएंट में काम करती हैं और हर क्लास की तरह इनलाइन style से हार जाती हैं; caret-*, placeholder:text-* और selection:bg-* इसी तरह caretColor, placeholderColor और selectionColor स्टाइल सेट करते हैं। Rust में ये Styled मेथड हैं (.number_of_lines(..), .truncate(), .text_overflow(..), .select_text(), .pointer_events(..), …)। whitespace-nowrap टेक्स्ट को क्लास के रूप में एक पंक्ति में रखता है। object-* इमेज और वीडियो की objectFit और objectPosition स्टाइल सेट करते हैं, जिन्हें स्टाइल की कुंजियाँ किसी अन्य की तरह बदल देती हैं।
  • समर्थित सेट: Tailwind 4 की वे यूटिलिटीज़ और वेरिएंट जिनका नेटिव अर्थ है। peer-* वेरिएंट, pseudo-elements (before:, after:) और मनमानी प्रॉपर्टीज़ ([mask-type:luminance]) अज्ञात क्लासेज़ के रूप में रिपोर्ट होते हैं, और हर वह यूटिलिटी भी जिसका NSH में कोई नेटिव अर्थ नहीं है, ताकि कोई क्लास कभी ब्राउज़र से अलग अर्थ न रखे। रेंडरिंग संकेत (antialiased, will-change-transform, appearance-none, box-border, …) स्वीकार किए जाते हैं और कुछ नहीं बदलते।

बाकी सब कुछ वैसे ही काम करता है जैसा Tailwind 4 में दस्तावेज़ित है: रंग पैलेट, opacity मॉडिफ़ायर (bg-black/40), मनमाने मान (bg-[#34c759], w-[72px], grid-cols-[1fr_2fr]; p, px, py, pt, pr, pb, pl और m यूटिलिटीज़ पर एनवायरनमेंट वेरिएबल भी, pt-[env(safe-area-inset-top)], pb-[calc(env(keyboard-inset-height)+49px)], देखें Safe area और Keyboard), भिन्न (w-1/2, translate-x-1/2), शैडो और रिंग, ग्रेडिएंट (bg-linear-to-r, bg-radial, bg-conic के साथ from- / via- / to-), फ़िल्टर, backdrop-blur-*, mix-blend-*, ट्रांसफ़ॉर्म, transition-* और duration-*, animate-spin / ping / pulse / bounce, और grid, हर एक Rust में अपने टाइप्ड मेथड के साथ। फ़िल्टर और ब्लेंडिंग की प्लेटफ़ॉर्म सीमाएँ क्लासेज़ पर भी लागू होती हैं।

#Theme

क्लासेज़ Tailwind 4 की डिफ़ॉल्ट थीम के विरुद्ध रिज़ॉल्व होती हैं। इसे nsh.json में "tailwind" के अंतर्गत बढ़ाएँ; आपकी प्रविष्टियाँ डिफ़ॉल्ट में जुड़ जाती हैं (या उसी नाम के डिफ़ॉल्ट को बदल देती हैं):

{
  "tailwind": {
    "colors": {
      "brand": { "50": "#f5f3ff", "500": "#7c3aed", "600": "#6d28d9", "DEFAULT": "#7c3aed" },
      "ink": "#1f2328"
    },
    "radius": { "card": 14 },
    "fontFamily": { "display": "Pacifico" },
    "breakpoints": { "tablet": 600 }
  }
}

इस थीम के साथ bg-brand-500, text-brand (DEFAULT शेड), border-ink, rounded-card, font-display और tablet:flex-row मान्य क्लासेज़ हैं।

कुंजीमानक्या बनाता है
colorsएक CSS रंग, या शेड्स का ऑब्जेक्ट (सादे नाम के लिए "DEFAULT")bg-*, text-*, border-*, from-*, ring-*, …
spacingप्रति स्पेसिंग स्टेप पॉइंट (डिफ़ॉल्ट 4)p-*, m-*, gap-*, w-*, …
remमनमाने मानों में प्रति rem पॉइंट (डिफ़ॉल्ट 16)
breakpointsपॉइंटname: वेरिएंट
containerपॉइंटmax-w-* साइज़
fontSizeपॉइंट, या [size, lineHeight] जिसमें लाइन की ऊँचाई आकार का गुणज होtext-*
fontWeight1–1000font-*
fontFamilyफ़ॉन्ट फ़ैमिली का नाम (सिस्टम फ़ैमिली या इम्पोर्ट किए गए फ़ॉन्ट का नाम)font-*
trackingEmstracking-*
leadingफ़ॉन्ट साइज़ के गुणजleading-*
radiusपॉइंटrounded-*
shadow, insetShadow, dropShadowएक CSS box-shadowshadow-*, inset-shadow-*, drop-shadow-*
blurपॉइंटblur-*, backdrop-blur-*
perspectiveपॉइंटperspective-*
aspectएक अनुपात (1.5)aspect-*
ease[x1, y1, x2, y2]ease-*

अज्ञात कुंजियाँ और अमान्य मान बिल्ड एरर हैं (nsh.json: tailwind: unknown theme key 'colours')। थीम बंडल में कंपाइल होती है और पहले फ़्रेम से पहले लागू हो जाती है।

Tailwind थीम आपकी क्लासेज़ को आकार देती है। टाइप्ड मेथड Tailwind के डिफ़ॉल्ट स्केल बनाए रखते हैं, ताकि Rust कंपोनेंट हर ऐप में एक जैसा दिखे, और ऐप का लुक अपनी थीम के डिज़ाइन टोकन से लेते हैं, जो nsh.json में "theme" है (Rust)।

#थीम टोकन

ऐप की थीम ("theme", nsh.json में) उसके डिज़ाइन टोकन रखती है: shadcn/ui के रंग (background, foreground, primary, muted-foreground, border, ring, chart-1, sidebar, …), उसका radius, उसके फ़ॉन्ट font-sans और font-mono, और ऐप द्वारा जोड़ा गया कोई भी टोकन। क्लासें इन्हें Tailwind नामों से इस्तेमाल करती हैं, जैसे CSS @theme के वेरिएबल नाम होते हैं; Rust कंपोनेंट इन्हें nsh_base से theme() के ज़रिए पढ़ते हैं, और TSX इन्हें nsh से token() के ज़रिए पढ़ता है, इसलिए TSX स्क्रीन और Rust कंपोनेंट एक ही थीम का पालन करते हैं:

use nsh_base::theme;

view()
    .rounded(move || theme().radius)
    .border_1()
    .border_color(move || theme().border)
    .bg(move || theme().card)
    .p_4()
    .child(text("Balance").text_color(move || theme().card_foreground))
    .child(text("Updated just now").text_color(move || theme().muted_foreground))
    .child(
        view()
            .rounded(move || theme().radius)
            .bg(move || theme().primary)
            .px_4()
            .py_2()
            .active(|s| s.bg(move || theme().primary.opacity(0.9)))
            .child(text("Continue").text_color(move || theme().primary_foreground)),
    )

टोकन का नाम उसके CSS वेरिएबल का नाम होता है, -- के बिना, जैसा थीम फ़ाइल उसे लिखती है: muted-foreground, radius, chart-1। Rust में shadcn के टोकन theme() के मेथड हैं (primary(), muted_foreground(), radius(), font_mono()), और हर टोकन theme().color("up"), theme().number("radius-card") या theme().font("font-display") होता है।

रूपक्या लेता हैउदाहरण
किसी भी रंग उपयोगिता में रंग का नामथीम का रंग, ओपेसिटी के साथ भीbg-primary, text-muted-foreground/70, bg-up/30, border-t-border, ring-ring, from-accent, placeholder:text-muted-foreground
(--name), Tailwind 4 का वेरिएबल शॉर्टहैंडथीम का रंग या संख्याbg-(--primary), rounded-(--radius), p-(--gap-card)
(color:--name), (length:--name), (number:--name), (family-name:--name)वही, टाइप के साथ, जहाँ कोई उपयोगिता कई प्रकार लेती हैtext-(color:--up), text-(length:--text-title), font-(family-name:--font-mono)
font-sans, font-monoथीम के फ़ॉन्ट, जब वह उन्हें सेट करती हैfont-mono
  • आपके अपने टोकन। थीम फ़ाइल का हर नाम एक टोकन है, ठीक वैसे ही जैसे CSS @theme का हर वेरिएबल: "up": "#16a34a" से bg-up, text-up और bg-up/30 बनते हैं; "radius-card": "14px" से rounded-(--radius-card) बनता है; "font-display": "Pacifico" से font-(family-name:--font-display) बनता है। लंबाइयाँ पॉइंट में होती हैं; (number:--name) सादी संख्या देता है।
  • फ़ॉन्ट। font-sans ऐप का फ़ॉन्ट है: जो भी टेक्स्ट अपना कोई फ़ैमिली सेट नहीं करता, वह इसी से बनाया जाता है, जैसे किसी पेज पर Tailwind का --font-sans लागू होता है। font-mono font-mono का और TextView में कोड का फ़ॉन्ट है। सेट न होने पर दोनों सिस्टम के फ़ॉन्ट होते हैं।
  • दोनों अपीयरेंस। टोकन के पास थीम का लाइट और डार्क दोनों मान होते हैं: bg-primary, theme().primary और token("primary") अपीयरेंस के साथ अपने आप बदल जाते हैं, इसके लिए किसी dark: क्लास की ज़रूरत नहीं। जो रंग थीम केवल एक अपीयरेंस में सेट करती है, वह दोनों में इस्तेमाल होता है।
  • आपके Tailwind रंग प्राथमिकता पाते हैं। "tailwind" colors या Tailwind पैलेट का कोई नाम वही रंग होता है, इसलिए bg-primary तभी थीम टोकन है जब Tailwind थीम में primary नाम का कोई रंग न हो।
  • ऐप चलते समय थीम बदल सकती है। जब नेटिव ऐप में एम्बेड किए गए ऐप को उस ऐप की थीम दी जाती है, और जब भी थीम बदलती है, तब टोकन नाम वाली क्लासें, theme() पढ़ने वाली Rust बाइंडिंग और token() पढ़ने वाला TSX कोड अपडेट हो जाते हैं, क्लासें और बाइंडिंग नेटिव रूप से। एम्बेड करने वाले ऐप की थीम में जो टोकन नहीं होते, वे ऐप के अपने "theme" के मान (फिर डिफ़ॉल्ट थीम के मान) रखते हैं। जो होस्ट केवल अपनी ब्रांडिंग जोड़ता है, वह थीम ओवरराइड देता है, यानी वे टोकन जिन्हें उसकी ब्रांडिंग उसी फ़ाइल फ़ॉर्मैट में बदलती है (iOS, Android, डेस्कटॉप पर AppView::set_theme_overrides)।
  • अन्य क्लासों की तरह जाँचे जाते हैं। जो नाम डिफ़ॉल्ट थीम का या ऐप के "theme" का टोकन नहीं है, वह बिल्ड के समय अज्ञात क्लास है (unknown class 'bg-chart-up')। ऐसा टोकन इस्तेमाल करने के लिए जो केवल एम्बेड करने वाला ऐप देता है, उसे ऐप के "theme" में भी एक मान दें: ऐप के अकेले चलने पर वही मान दिखता है।

टोकन नाम वाली क्लासें हर थीम के लिए एक बार हल की जाती हैं, किसी भी अन्य क्लास की तरह: इन्हें ड्रॉ करने की लागत उतनी ही है जितनी पैलेट के रंग की।

#TSX में token()

nsh से token(name) दिखाए गए अपीयरेंस में टोकन का मान देता है: रंग #rrggbbaa के रूप में, लंबाई पॉइंट की संख्या के रूप में, फ़ॉन्ट उसके फ़ैमिली नाम के रूप में। यह रिऐक्टिव है, इसलिए जो कोड इसे किसी कंपोनेंट, मेमो या इफ़ेक्ट में पढ़ता है, वह थीम या अपीयरेंस बदलने पर फिर चलता है। यह उन मानों के लिए उपयुक्त है जो वहाँ जाते हैं जहाँ कोई क्लास नहीं जाती, जैसे कैनवास या चार्ट:

import { Canvas, token } from "nsh";

<Canvas
  style={{ height: 160 }}
  draw={(cx, { width, height }) => {
    cx.beginPath();
    cx.moveTo(0, height);
    cx.lineTo(width, 0);
    cx.strokeStyle = token("chart-1");
    cx.lineWidth = 2;
    cx.stroke();
  }}
/>

नाम टाइप किए हुए हैं: NSH ऐप के टोकन उसकी थीम से .nsh/types/nsh/tokens.d.ts में लिखता है, इसलिए थीम में "up" होने पर token("up") एक string होता है, और थीम में जो नाम नहीं है, वह टाइप एरर है।

#क्लास जाँच

nsh build, nsh dev, nsh run और nsh test आपके JSX में स्टैटिक क्लास नामों (class में स्ट्रिंग लिटरल और classList की कुंजियाँ) को उसी पार्सर और थीम से जाँचते हैं जो रनटाइम इस्तेमाल करता है, और जो क्लासें मौजूद नहीं हैं उनके बारे में चेतावनी देते हैं:

 warn nsh::build App.tsx:12:36: unknown class `bg-brand-550`

चेतावनियों से बिल्ड फ़ेल नहीं होता। रन टाइम पर बनी क्लासें (bg-${color()}-500) तब जाँची जाती हैं जब रनटाइम उन्हें पहली बार देखता है, और वह हर अज्ञात क्लास को एक बार लॉग करता है (unknown classes: ...)। टाइप किए गए मेथड Rust कंपाइलर जाँचता है।

#क्लासें या स्टाइल?

दोनों नेटिव रूप से हल होते हैं और लगभग समान लागत वाले हैं। क्लासें छोटी होती हैं और बिना कोड के वेरिएंट (प्रेस्ड स्टेट, डार्क मोड, ब्रेकपॉइंट) देती हैं। इनलाइन स्टाइल JavaScript में गणना किए गए मान लेते हैं और नेटिव एनिमेटेड मान (opacity: fade, translate: [x, 0]) स्वीकार करते हैं। अधिकांश TSX ऐप स्थिर रूप के लिए क्लासें और गणना किए गए या एनिमेटेड मानों के लिए style इस्तेमाल करते हैं। Rust कंपोनेंट दोनों के लिए टाइप किए गए मेथड इस्तेमाल करते हैं, और एनिमेटेड मानों को .bind_opacity(..), .bind_translate_x(..) और अन्य बाइंडिंग से बाइंड करते हैं।

#संबंधित

  • Styling: स्टाइल प्रॉप, टाइप किए गए मेथड और इफ़ेक्ट की प्लेटफ़ॉर्म सीमाएँ।
  • Tailwind संदर्भ: हर उपयोगिता और वेरिएंट।
  • Layout: फ़्लेक्सबॉक्स और ग्रिड, जो क्लासों के रूप में भी उपलब्ध हैं।