Development notes
Who maintains a UI component after you copy it into your project?
Using this site’s local Button, shared tokens, and CLI configuration, this note examines how to manage custom changes, dependencies, and update scope without treating an upstream version as a guarantee of site-wide behavior.
- Component design
- bejamas/ui
- Maintenance
- Astro
Once a component’s source code is in your project, changing a border radius or a default attribute is easy. When the next upstream version arrives, the question changes: which parts can you replace, and which already define how your own site works?
This site uses the copy-and-own approach provided by bejamas/ui. The CLI is the entry point for obtaining components; pages actually import the source code inside the project. The Bejamas CLI documentation likewise describes adding components as writing them to the locations specified by aliases, with --dry-run available to preview what will be written.[1]
I treat these components as local code to maintain. Upstream remains a useful reference, but update decisions need to account for how the component is used, not just its version number.
Customization gives a component its own contract
This site’s Button supports as="a", as="button", and as="summary", so callers can choose an element to suit its purpose. If as is omitted, it renders a button. When it renders a button, it sets type="button" by default, while still allowing the caller to explicitly override it with type="submit".
These choices already affect how pages use the component. Demo links on project cards pass as="a" and href; the control that enlarges the avatar on the About page is an action button. Replacing Button with another file that looks the same does not establish that all these uses still work as intended.
Button also uses the site’s color and motion tokens and retains branches for focus-visible and reduced-motion. These are parts of the local implementation described here, not defaults that should be assumed to exist in every upstream version.
Maintenance therefore needs a traceable record of the differences: which defaults changed, which usage sites depend on them, and how to check the output. The site’s documentation already records Button’s local type behavior. If that contract changes again, the documentation should change with it rather than leaving the decision buried in a conversation.
The CLI, component, and dependencies have separate updates
This site lists the Bejamas CLI as a development dependency and invokes it through a project-defined ui:add script. The local Button implementation imports class-variance-authority and cn; cn in turn uses clsx and tailwind-merge.
The relationship can be simplified to:
CLI → retrieve or generate source code
Pages → local Button → variants and class-merging helper
Updating the CLI does not update every local Button. Keeping the Button file does not mean it no longer depends on other packages, either. These need separate checks.
The manifest recorded here pins the CLI version, and the lockfile preserves the resolved dependencies. Neither guarantees the component content fetched remotely next time. A safer approach is still to preview the planned writes and then review the actual diff, rather than skipping that step because the tool version is pinned.
The initial integration note explains how we connected one Button to the site. This note is about who keeps maintaining the differences after that integration.
Before updating, list what must not be overwritten
I would first write down a short local contract, then compare it with the upstream candidate. For this site’s Button, the following table gives maintenance checks to consider. It does not report an upgrade performed for this note.
| Responsibility to preserve | What to inspect during comparison |
|---|---|
| Navigation remains a real link | as="a", href, and the actual output tag |
| Actions do not submit forms unintentionally | The local type="button" default and explicit overrides |
| Callers can adjust styling | The merged result of variants, class, and cn |
| Colors retain a single source | The semantic tokens and shared styles the component references |
| Keyboard access and reduced-motion preferences | Focus indicators, state styles, and reduced-motion branches |
A shared component change can affect several pages, so comparing only a demo page is not enough. If a candidate changes prop names, slots, or data attributes, follow the existing usage sites and check whether they rely on those interfaces to set destinations, appearance, or behavior.
Necessary upstream fixes can be merged without replacing the whole file. If only one piece of logic is needed, keeping the local contract and making a narrow change usually makes it easier to explain exactly what changed. That is my maintenance trade-off, not a measurement of update costs across all projects.
Self-contained source can still have site-wide style effects
This site centralizes semantic color mappings in shared CSS. Button references those names instead of repeating a set of color codes on every page. That lets the component follow the site’s design, but it also means a token change can reach beyond a single Button.
The same applies when adding a component. What appears to be one new .astro file may also bring changes to helpers, dependencies, and global CSS. The site’s integration rules therefore require reviewing the CLI’s planned writes, rejecting an automatic wholesale base reset, and avoiding blind overwrites of customized components.
I separate “Which styles does this component need?” from “Should this change alter styles across the site?” The former may identify necessary dependencies; the latter still needs its own reason and scope.
Without a new requirement or a necessary fix, an extra upstream variant does not oblige the site to add it. Maintenance is about keeping the contract we use intact, not importing every new option.
Checks should follow actual usage
Button’s tags, attributes, and class merging can be inspected in the rendered HTML. Whether focus is clear and touch controls are easy to use still needs checking in the relevant interface. Even a full site build may never render a variant that no page uses.
Interactive components have further responsibilities. This site uses Popover for the author introduction and Drawer for the article’s table of contents. Checks of Button’s output cannot substitute for verifying how those components open, handle focus, and close.
An update handoff should say which local contract changed, which real usage sites were checked, and which interactions or visual details still need confirmation. That gives the next maintainer a clear place to continue instead of just being told “the component has been updated.”
Scope of this record
This note draws on the site’s Button, class-merging helper, CLI configuration, shared tokens, and page usage sites as recorded in the original. The comparison table is maintenance guidance. No components were upgraded, no registry content was fetched again, and no maintenance hours were measured for this note. It also does not claim that all existing interactions have passed a new round of acceptance checks.
Copy-and-own gives us room to change code directly while leaving the project responsible for its local differences. My choice is to preserve a contract we can explain, then decide which upstream changes to adopt.