Nasaq

EnvList

A .env manager with masked values, reveal and copy, add edit delete, import by pasting a .env, export, per-environment tabs and key validation.

PreviewOpen ↗

Code

import { EnvList } from "@fadymondy/nasaq/web";export function Env() {  return (    <EnvList      variables={[{ key: "DATABASE_URL", value: "postgres://...", secret: true }]}      onSave={async (v, previousKey) => {}}      onDelete={async (key) => {}}      onImport={async (vars, { overwrite }) => {}}    />  );}

Developer Tools · stable

Live examples and controls: EnvList in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/env-list.json

Manages environment variables as key and value rows. Values are masked with a fixed-width mask (so length is not leaked) and only enter the DOM while revealed; a revealed value hides again after 30 seconds. Each row has reveal, copy, edit and delete. Add and edit open a dialog that checks the key (empty, invalid, duplicate). Import takes a pasted .env or a chosen file, shows what was found, flags conflicts and unreadable lines, and lets the user choose whether to overwrite. Export downloads a .env. Optional tabs switch environments. Nothing is logged, and error messages never include a value. Presentational: your callbacks talk to the backend.

When to use

  • Project or service settings where a developer edits environment variables.

When not to use

  • API keys with a create-once, show-once flow: use ApiKeys.
  • A single value to copy: use CopyField from copy-button.

Import

import { EnvList } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"

Quick start

import { EnvList } from "@fadymondy/nasaq/web";

export function Env() {
  return (
    <EnvList
      variables={[{ key: "DATABASE_URL", value: "postgres://...", secret: true }]}
      onSave={async (v, previousKey) => {}}
      onDelete={async (key) => {}}
      onImport={async (vars, { overwrite }) => {}}
    />
  );
}

Anatomy

EnvList         data-slot="env-list"          <section>
├─ header       title, description, environment tabs, Add / Import / Copy / Download
├─ filter       search, shown when there are more than 5 variables
├─ rows         data-slot="env-row"           key, masked value, reveal, copy, edit, delete
├─ add / edit   Dialog                        key and value, validation, secret switch
├─ import       Dialog                        paste or file, preview, overwrite checkbox, issues
└─ delete       AlertDialog                   confirm

API

PropTypeDefaultDescription
variablesEnvVariable[]required{ key, value, secret?, description? }. secret defaults to true.
title? / description?ReactNode"Environment variables"Header text.
environments?{ id, label }[]noneTabs. Load the matching variables when onEnvironmentChange fires.
environment? / onEnvironmentChange?string / (id) => voidfirstSelected environment.
onSave?(variable, previousKey?) => Promise<{ error? } | void>noneAdd (no previousKey) or edit. { error } keeps the dialog open.
onDelete?(key) => Promise<{ error? } | void>noneAfter confirmation.
onImport?(variables, { overwrite }) => Promise<{ error? } | void>noneImported values are secret unless the key is public (NEXT_PUBLIC_*, VITE_*).
onExport?(variables) => voidsaves a fileReplaces the default download.
exportFilename?string".env"Default download name.
revealTimeout?number30000Milliseconds until a revealed value hides. 0 keeps it.
readOnly?booleanfalseNo add, edit, delete or import. Reveal and copy still work.
labels?Partial<EnvListLabels>built-in en/arTranslations.

Helpers: parseEnv(text) returns { variables, duplicates, issues } (issues carry a line number and a problem, never a value), serializeEnv, quoteEnvValue, checkEnvKey, isValidEnvKey, conflictingKeys, looksPublic, MASK.

Examples

Environments

<EnvList
  environments={[{ id: "dev", label: "Development" }, { id: "prod", label: "Production" }]}
  environment={env}
  onEnvironmentChange={setEnv}
  variables={byEnv[env]}
/>

Parsing

parseEnv('A=1\nexport B="two words"\nA=3').duplicates; // ["A"], the last value wins

Accessibility

KeyAction
TabRow buttons in order
Enter / SpaceReveal, copy, edit or delete
EscCloses a dialog
  • Reveal is a toggle button with aria-pressed and a name that includes the key. Masked values are aria-hidden text plus the button label, so a mask is never read out as twelve bullets.
  • Validation messages are tied to the key field with aria-describedby and announced.
  • Delete asks first, in an alert dialog that names the key.

RTL & i18n

  • Keys and values are dir="ltr" inside Arabic pages; the chrome follows the page.
  • Built-in Arabic strings; override with labels.
  • Buttons sit on the logical inline-end side.

Styling & tokens

  • Rows use bg-card, border-border, text-code for keys and values; conflict and issue text uses text-nq-warning-text and text-nq-danger-text.
  • Slots: env-list, env-row. data-revealed on a row.

Do / Don't

  • Do keep values out of logs, analytics and error messages, in your callbacks too.
  • Do return { error } with a safe message from callbacks.
  • Don't render this for people who should not see secrets: masking is not access control.
  • Don't use revealTimeout={0} on shared screens.

Lab

https://docs.nasaqui.com/?path=/docs/components-developer-tools-env-list--docs

On this page