GeistHaus
log in · sign up

AlanHogan.com

Part of feedburner.com

Alan Hogan's website - Web development, Mac user tips, etc.

stories
How I Lent Scammers a Domain

It began when my domain registrar contacted me. A domain I bought years ago and never really used, they informed me, was currently being used for phishing, which is against my registrar's terms of service.

Hmm. I’m not a black hat, so what was going on?

Logging in to my registrar (NameCheap) I was able to see that my domain's DNS was pointing at DreamHost, a shared web host I use for some websites. However, when logged into DreamHost, the domain in question was not listed.

I figured that the most likely way this came about was this:

  1. I pointed my domain name at my web host, intending to show a parking page.

  2. I never set it up at my web host, by accidental oversight.

  3. Someone else noticed the default DreamHost parking page and successfully convinced DreamHost to add the domain to their account.

What to do? The most obvious course of action was to reach out to my host. However, their support was unreachable! The contact form required me to select the domain in question, but the domain wasn’t in my account, it was in a scammer's. Then, I tried to use a different contact method for security and abuse, but this contact form was literally broken and could not be submitted.

I worked around my inability to resolve the situation with my host by using CloudFlare's free static site hosting instead, changing the DNS records at the registrar to bypass DreamHost entirely.

(Later I heard back from DreamHost after guessing at abuse-related email addresses in order to contact their abuse team, who told me they were already on it and had shut down the account used for phishing.)

My registrar was then satisfied, and I had done my part to reduce phishing on the Internet.

https://alanhogan.com/phishing-on-my-domain
A Modern HTML Template (2022)

Copy-paste this minimal, modern template to start a new HTML5 web page with automatic use of system fonts, preference-respecting color themes, and mobile-friendly responsive sizing. Alternatively, just use the CSS for a quick entry into a modern look with dark mode support.

I recommend starting here when you are working on a proof of concept, spinning up a new website, or authoring a one-off web page. You’ll be effortlessly opting into good reading experiences on desktop and mobile, banishing that default Times New Roman look, and saving your eyes with dark mode for those late-night hacking sessions.

The CSS in particular is good to drop into a new CodePen, JSFiddle, JSBin, or other playground. I do it all the time! The links in this paragraph will take you to a template you can easily fork for your next experiment or demo.

HTML
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Untitled</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- <meta name="description" content="TODO" /> -->
    <!-- <link rel="stylesheet" href="TODO" /> -->
  </head>
  <body>
    <h1>Untitled</h1>

    <p>A nearly-blank page beginning with <code>&lt;!doctype html&gt;</code></p>

    <p>
      <a href="https://alanhogan.com/code/modern-html-template"
        >Based on the modern HTML template</a
      >
      by <a href="https://alanhogan.com/">Alan Hogan</a>
    </p>
  </body>
</html>
CSS
/* Automatic dark mode, system fonts, and readable max-width
 * via https://ajh.us/template */

:root {
  --page-bg-color: white;
  --text-color: #222;
  --link-color: #06d;
  --visited-link-color: #91e;
  --code-color: #456658;

  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root {
    --page-bg-color: #141414;
    --text-color: #eee;
    --link-color: #2af;
    --visited-link-color: #c5f;
    --code-color: #c5eddc;

    /* Ask browser to render elements like inputs & scrollbars per dark theme */
    color-scheme: dark;
  }
}

* {
  box-sizing: border-box;
}
html {
  margin: 0;
  padding: 0;
  -webkit-text-size-adjust: 100%;
  -moz-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
html,
body,
input,
button,
select,
textarea {
  font-family: system-ui, sans-serif;
}
body {
  padding: calc(0.5em + 0.5vmin);
  margin: 0 auto;
  max-width: 40em;
  background-color: var(--page-bg-color);
  color: var(--text-color);

  /* Good defaults for handling too-long words; won't help in most tables */
  overflow-wrap: break-word;
}

:link {
  color: var(--link-color);
}
:visited {
  color: var(--visited-link-color);
}

code,
tt,
kbd,
pre {
  font-family: "JetBrains Mono", "IBM Plex Mono", "Source Code Pro",
    "Cascadia Code", ui-monospace, Menlo, "Ubuntu Monospace", "Inconsolata",
    "Fira Code", "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", Monaco,
    "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", Consolas, monospace;
  /* Fight famous 'code too big' problem */
  font-size: 90%;
  color: var(--code-color);
}
pre code,
pre tt,
pre kbd {
  /* We don't want 90% of 90% for <pre><code> */
  font-size: 100%;
}

/* End automatic dark mode, system fonts, and readable max-width */
Preview

This is an iframe showing the above snippets in action.

<b>A preview will appear here if JavaScript is enabled.</b> Notes
  • This template indicates that content will be in English (en). HTML documents without a lang attribute aren’t valid.
  • It declares a UTF-8 charset, which you should probably be using anyway.
  • The body is set to a default maximum width of 40 em, a great default for readable line widths. If this is limiting, move the relevant max-width line so that it applies to some sort of wrapping container instead.
  • A responsive viewport is declared, meaning no side-scrolling should be necessary on mobile. Users can still pinch to zoom if desired. Notably, if you drop in big elements such as a large image, they will still cause side-scrolling without additional code.
  • Using text-size-adjust, we opt out of mobile browsers mucking with font sizes. These algorithms often result in unsightly, inconsistent scaling, and we don’t need them, because we are responsive out of the gate.
  • Dark mode is supported out of the box using CSS variables and the prefers-color-scheme media query. Natively drawn UI such as input elements and scrollbars will render in a dark-mode-friendly manner thanks to the color-scheme property.
  • Minimal base CSS styles are provided, including body background and text colors and unvisited and visited link colors. Most elements will display per browser default styles.
  • All elements are initialized to use border-box sizing.
  • All colors are set using CSS variables, which will fail to apply in Internet Explorer, Edge < 15, Android Browser < 5, and Opera Mini (where browser defaults will apply instead). The vast majority of Internet users are now in browsers that support CSS variables.
  • I provided a commented-out meta description tag in the head. Use it to suggest content for search engine results pages.
  • This HTML (check) and CSS (check) pass validation.
  • In early drafts of this post, I had included a no-js utility class on html and then removed it with JavaScript. However, in keeping with this template’s goals of being modern, minimal, and best-practice oriented, I am no longer including this code. Instead of using a class on HTML in order to show/hide content based on the availability of JavaScript, I would encourage the use of progressive enhancement (adding or changing the UI with JS, when enabled, and gracefully handling the no-JS experience when possible) and noscript tags (a built-in way to provide content for browsers without JS enabled or supported).1
Modern means slim

Here are some things that you might have seen here if I created this template a decade or so earlier:

  • A directive opting into the latest MSIE rendering engine.2 You don’t need this anymore:
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

  • An XML namespace and/or XHTML doctypes

  • MIME types in <link rel="stylesheet"> and <script> tags

  • An “HTML5 reset” to help old browsers play more nicely with new HTML tags

  • Any sort of polyfill or feature detection

Are you familiar with the HTML5 Boilerplate project? In many senses it is a direct competitor to my much slimmer template, which is like an HTML5 boilerplate without the boilerplate. You might still want to check out that project if you are embarking on a big project, because it comes with a lot more. (But maybe you don't want or need more.) It used to come with jQuery until 2020. It still ships with Modernizr (feature detection), an NPM package.json file, an iconset, and a lot more that you may or may not actually want. My humble offering is a fraction of the size and complexity — more focused on table stakes, not the whole menu.

What you might want to add
  • OpenGraph meta tags for richer link previews in social media: I’m not an expert here, but I think this is a good place to get started.

  • Manual dark/light mode switching: See this codepen for a working example that doesn't require JavaScript, or this one for a JavaScript-based solution that detects the user's system preference and allows persistent overrides with localStorage (I really like this one!).

  • Feature detection-based warnings, errors, or redirections for users of incapable browsers: This is a topic for another day, but I strongly recommend using the technique of 'feature detection' for required web platform features and 'progressive enhancement' and/or 'graceful degradation' for features that 'merely' improve the experience.

  • More typographical love: Web typography is an enormous topic, but if you have any narrow columns in your layout, you may want to consider applying hyphens: auto; (and -webkit-hyphens: auto) to them, to allow long words to break gracefully with hyphenation, newspaper-style.

Final thoughts

Web development has, in many ways, gotten incredibly complex over the last 20 years or so, with long chains of technologies used for development and deployment of web apps. But the simple meat and potatoes of web technologies — “vanilla” HTML, CSS, and JS — these have gotten simpler and easier in the years since the HTML5 project was undertaken (and since Microsoft abandoned Trident). Why? Because HTML5 was a huge success. As I view it, HTML5 was an effort to (1) pave the cowpaths of how people actually write HTML instead of insisting on theory-based adherence to specifications, and (2) standardize all the different browsers in terms of how they handle edge cases, malformed code, and web standards in general. In these aims HTML5 has been hugely effective. It means you really can start making beautiful, performant websites that meet users on their preferred devices (and with their preferred color scheme!) using incredibly few lines of code. For that, I am grateful.

Thanks to Matthew Chavez, Louis Cruz, and Greg Hogan for feedback on a draft of this post.

const preEls = document.querySelectorAll("#content pre"); const html = preEls[0].innerText; const css = preEls[1].innerText; // SHOW IFRAME const cssComment = /<!--[^\n]+stylesheet[^\n]+-->/; const replaced = html.replace(cssComment, "\u003Cstyle>" + css + "\u003C/style>"); const iframeHolderEl = document.getElementById('iframe-holder'); const iframe = document.createElement('iframe'); iframeHolderEl.replaceChildren(iframe); const frameDoc = iframe.contentWindow ? iframe.contentWindow.document : iframe.contentDocument; frameDoc.write(replaced); frameDoc.close(); iframe.style = "width: 100%; border: 1px solid #888; min-height: 12em;"; // ADD BUTTON TO EXPAND/SHRINK IFRAME const widenText = '← Widen iframe (desktop only) →'; const shrinkText = '→ Narrow iframe (desktop only) ←'; const sizeToggleButtonEl = document.createElement('button'); sizeToggleButtonEl.classList.add('button', 'button--skinny'); sizeToggleButtonEl.innerText = widenText; const sizeToggleWrapEl = document.createElement('p'); sizeToggleWrapEl.appendChild(sizeToggleButtonEl); const iframeWrap = document.querySelector('.js-iframe-wrap'); iframeWrap.parentNode.insertBefore(sizeToggleWrapEl, iframeWrap); sizeToggleButtonEl.addEventListener('click', (e) => { if (iframeWrap.classList.contains('large-picture-wrap')) { iframeWrap.classList.remove('large-picture-wrap'); sizeToggleButtonEl.innerText = widenText; } else { iframeWrap.classList.add('large-picture-wrap'); sizeToggleButtonEl.innerText = shrinkText; } }) // ENHANCE DISPLAY AND ADD COPY BUTTON preEls.forEach((el) => { // reasonable heights for any screen el.style.maxHeight = '55vh'; el.style.height = '24em'; el.style.minHeight = '8em'; el.style.overflowY = 'auto'; // Add copy buttons const newDivEl = document.createElement('div'); newDivEl.style = `text-align: right; padding-bottom: 0.4em`; const newButtonEl = document.createElement('button'); const normalButtonText = 'Copy to Clipboard'; newButtonEl.classList.add('button', 'button--skinny'); newButtonEl.style.minWidth = '12em'; // avoid jitter when text changes newButtonEl.innerText = normalButtonText; newButtonEl.addEventListener('click', (e) => { if (navigator.clipboard) { navigator.clipboard.writeText(el.innerText).then(() => { newButtonEl.innerText = 'Copied!'; setTimeout(() => { newButtonEl.innerText = normalButtonText; }, 2000); }); } else { newButtonEl.innerText = 'Failed'; newButtonEl.disabled = true; setTimeout(() => { newButtonEl.innerText = normalButtonText; newButtonEl.disabled = false; }, 2000); } }); newDivEl.appendChild(newButtonEl); el.parentNode.insertBefore(newDivEl, el); });
  1. If you really want the .no-js class, let me know why, would you? You can do it by adding class="no-js" to <html> and adding these lines within the head:

    <script>
      document.documentElement.classList.remove("no-js");
    </script>
    

    ↩︎

  2. Previously on AlanHogan.com ↩︎

https://alanhogan.com/code/modern-html-template
Abbreviations in Color Variable Names

Now that we can rely on browsers to support CSS variables, we can define and use color themes by simply defining role-based color name variables. (For example, that is what powers this dark mode demo, although the naming scheme here isn't used.)

My humble suggestion is to use the following conventions. They attempt to maximize terseness, consistency, and predictability. YMMV

Abbreviation Meaning Example txt text color (color in CSS) --btn-txt bg background color --inp-bg bdr border color --inp-fcs-bdr rds border radius --inp-rds hvr :hover --btn-hvr-txt fcs :focus or :focus-visible --inp-fcs-bdr actv :active --btn-actv-txt err error state --inp-err-hvr-bdr inp (text) input --inp-hvr-bg btn button --btn-actv-bg

I also suggest consistently naming variables in the following order:

  1. Specific module targeted, if applicable (inp, btn), or skip this for a global default (page background color is just --bg)

  2. Variant of the module being targeted (small, secondary, dangerous), if applicable

  3. Long-term state modifiers, if applicable (err)

  4. Short-term state modifiers, often tied to CSS pseudoclasses, if any (hvr, fcs)

  5. Property being set (txt, bdr)

This ordering is demonstrated in every example in the table above.

History

Shortly after publication, bg-clr, txt-clr, bdr-clr, and bdr-rds were simplified to bg, txt, bdr, and rds, given the color-focused nature of these variables overall and the redundancy of the clr portion. Yes, a border can include more than color, but need that be captured in a variable? Not really for theming, right? If you do need to capture an entire background or border value in a variable, you can simply use the non-abbreviated spelling.

Feedback

Your thoughts are welcome! Was this useful? Is there something you would change? Use my contact form.

Share

Try introducing this to your teammates or sharing it with someone else who works with CSS!

https://alanhogan.com/code/css/color-variable-names
New Bookmarklets Available

Bookmarklets, as I say on my main bookmarklet page, “are scripts that affect the current page you are viewing” and are saved in your browser as if they were bookmarks — although bookmarklets don’t take you to another page.

I published my bookmarklets collection back in 2009, and have added and updated them over the years. Some are quite useful; some are rather niche. Regardless, the collection has grown considerably so I just wanted to publish a quick note calling attention to them.

Take a look. Newer bookmarklets are at the bottom. The source code for all of them is on GitHub.

https://alanhogan.com/conversations/bookmarklets-2022
Capitalization of Vendor-Prefixed CSS Property Names in JavaScript

I just spent a little too long chasing down the answer to this question: When using an element’s JavaScript styles interface, the .style property, should vendor-prefixed property names start with a capital letter or not? E.g., is it button.style.webkitFilter or button.style.WebkitFilter?

The Short Answer

Capitalize them.1

However, if the prefix is Webkit, case does not matter.

Case matters for Moz.

(No other vendor prefix besides these two really matters anymore.)

The Long Answer

Here is more information for the curious. Note that I expect the reader to be familiar with the general history of the major web browsers and their rendering engines.

At some point, it seems that the thinking (which I would tend to share) is that they should start with a capital letter.

You can find old blog posts and StackOverflow answers that carefully recommend prefixes (when needed) that follow the table below:

Vendor prefix CSS JS Microsoft -ms- ms Mozilla -moz- Moz Opera -o- O Webkit -webkit- Webkit

And, to a large degree, this was and is correct.

However, things have changed.

  1. Mozilla and Opera announced, all the way back in 2012, that they were going to start supporting some Webkit-prefixed properties.

  2. Non-Webkit browsers2 realized that a lot of web developers were only using Webkit prefixes even when they introduced their own prefixes. In 2016, for example, Mozilla announced they had released a version of Firefox that added support for a bunch of Webkit-prefixed properties.

  3. Generally speaking there’s been a big recognition that vendor prefixes in general should be minimized and the unprefixed version should be supported as soon as there’s a consensus around the syntax.

  4. A lot of the non-Webkit browsers are now… Webkit browsers. Microsoft Edge is now a Chromium browser, not Trident. Opera’s full browser is now Chromium browser.

  5. At some point — right away? After a year? I have no idea — browsers began treating style.WebkitX and style.webkitX as synonyms.

  6. Last but not least, Apple seems to have deleted a lot of old Safari documentation from its website, leaving me unable to find a definitive source from the company that invented the -webkit- prefix!

All of this leads us to a point where, in virtually any browser, you if you are going to set a vendor-prefixed CSS property using JavaScript, it’s going to respond to both webkit and Webkit prefixes.

Yes. Even in Firefox.

For example, in Firefox, you can run the following lines of JavaScript; the result is commented:

var button = document.querySelector("button");
button.WebkitFilter = "blur(1px)"; // It blurs it a little
button.webkitFilter = "blur(2px)"; // It blurs it more
button.WebkitFilter = "blur(3px)"; // It blurs it even more
button.filter = "blur(4px)"; // It blurs it even more
button.WebkitFilter = "blur(0)"; // No more blur!
button.MozFilter = "blur(1px)"; // No effect!

Here we have three supported synonyms for the same property. Changing one changes them all. Notably there is not even support for the Moz prefix, presumably because Firefox never supported filter until it was far along enough in standardization to be supported unprefixed, and virtually nobody authored CSS with a Mozilla-specific prefix but not the unprefixed property.

Now there are still CSS properties that are only supported in Firefox/Gecko with a vendor prefix and only with the -moz- prefix. In JavaScript, you must use the Moz prefix. Example, as of Firefox 99 (April 2022):

btn.style.MozBorderEnd = "2px solid yellow"; // Right border is yellow
btn.style.mozBorderEnd = "1px solid blue"; // No effect
btn.style.borderEnd = "1px solid orange"; // No effect
btn.style.WebkitBorderEnd = "1px solid red"; // No effect

So in summary, this is where we stand today:

Vendor prefix CSS JS Microsoft Defunct; now Chromium (Webkit) Mozilla -moz- Moz (case matters) Opera Defunct; now Chromium (Webkit) Webkit -webkit- Webkit, but webkit works too, even in Firefox

Please let me know if I’ve overlooked something important, or if you’ve arrived from the future and the sitation has changed further from when I wrote this in 2022. Thanks!


  1. The exception was Microsoft (see table). Note the past tense in that sentence. No current Microsoft browser uses Microsoft vendor prefixes, so this is only relevant if you are, for whatever reason, supporting ancient, specific, rarely used versions of Microsoft browsers. ↩︎

  2. Blink is the Webkit fork at the heart of Chromium browsers. Technically it is not Webkit anymore, but it still uses the same -webkit- prefix that Webkit does, so for our purposes Blink counts as Webkit. When I say “non-Webkit” browsers, I mean browsers that did not share a history with Webkit. ↩︎

https://alanhogan.com/code/vendor-prefixed-css-property-names-in-javascript
ZASM: Zero-Ambiguity Stylesheet Methodology
Introduction

In this document I hope to define a CSS methodology using a small set of unambigous rules. This methodology has these aims:

  • extreme maintainability
    • high readability
    • guided writability
    • low barrier to adding collaborators
  • broad compatibility
    • with mobile-first patterns
    • with responsive designs
    • with pre-processors and build tools
    • with modern web standards
    • with backwards compatibility
    • with legacy codebases as well as green-field projects

The first major goal is maintainability — important whether your project involves one person or ten. For any sort of styling you want to do, under ZASM, there should be one clear, “right’ way to write the selector for it; this makes the CSS easy to author. Class names written under ZASM are designed to be instantly identifiable as belonging to one of the categories defined in the methodology. This provides readability and helps communicate author intention. By keeping the methodology small and tightly defined, new collaborators won’t face a large uphill climb to productivity — at least, assuming they have already learned enough CSS, an admittedly complicated pre-requisite!

The second top-level goal is broad compatibility. That means that ZASM should be able to be the one methodology you can reach for in all your projects, both shared and personal. To do this, we carefully establish rules that are strict enough to provide some built in ‘defenses’ as well as flexible enough to adapt to any specific project.

Base Styles

In as little CSS as possible, and by writing selectors without any class names at all (pseudo-classes are OK), set base element styles including your preferred font stack.

It is recommended to set a good base line height. It is up to you whether that is a ratio (1.2, 1.6, etc) or an absolute measurement (24px).

If desired, style links and form controls (input elements). This is not necessary because you can instead ensure every link and control is styled by a module; it is up to your preference.

In your base styles, fix dumb browser default styling issues. Optionally set sane defaults for box-sizing and overflow-wrap on everything.

If using CSS variables, set globally applicable variables, or even all variables, here. Your color palette is the most obvious thing to define here. Optionally modify these using media queries and/or classes applied to html (aka :root). It is recommended that any such class names start with theme-: theme-dark.

Notably, it is perfectly okay under ZASM if you avoid CSS variables altogether. You do not need to support multiple themes at all. Or you can ship a hundred different themes all compiled as different .css files using Sass. It doesn’t affect the rest of the methodology at all.

ZASM recommends writing fewer base styles and more modules.

:root {
  --page-bg-color: #def;
  --text-color: #222;
}

/* Automatically use dark mode if the user prefers it */
@media (prefers-color-scheme: dark) {
  :root {
    --page-bg-color: #123;
    --text-color: #eee;
  }
}

/* Note, redundant variables to automatic dark mode for when
 * dark mode is manually applied to <html> by class */
:root.theme-dark {
  --page-bg-color: #123;
  --text-color: #eee;
}

:root.theme-halloween {
  --page-bg-color: black;
  --text-color: orangered;
}

:root, body, input, textarea, button {
  font: 16px/1.4 "Brand Font", system-ui, sans-serif;
}  

* {
  box-sizing: border-box;
  overflow-wrap: break-word;
}

:link { color: blue; }
:visited { color: rebeccapurple; }
:link:active, :visited:active { color: var(--text-color);
Modules

Modules are things — just about anything. They are composable and can be placed within each other (when it makes sense). They can be very complicated. They can have parts and variants.

The naming rule for a module's class name (or "base class") is: any three-letter class name.

However, you should add a prefix such as m- for all module names when introducing ZASM to codebases with existing HTML that uses unprefixed class names: .m-mdl instead of .mdl and .m-mdl__header instead of .mdl__header.

If your organization is adopting ZASM, there may be cases where you may not desire or be able to follow the three-letter naming rule. That is an acceptable deviation; just remember to make your module naming rule clear enough that it is readily understood to be a module when it is seen in use. And try to avoid long module names as it makes naming parts and variants very clunky.

.mdl { /* Base class for a "Modal" module */ }
.mdl__header { /* A part called "header" within an "mdl" modal */ }
.mdl--alert { /* Variant of "mdl" fulfilling "alert" purpose */ }
Parts

In ZASM, a "part" is what BEM calls an "element": It's something that belongs within its module. I call them "parts" because "element" already has a different meaning in CSS and HTML.

Parts are always named like this: base module name + two underscores + kebab-cased part name.

Example: Module foo could have parts foo__intro and foo__author-signature.

A module can have unlimited parts. Parts can be within other parts — for example you might have a close button .mdl__close within the modal header .mdl__header. Whether the parts of the module are required or optional is up to you.

Variants

"Variants" are variations of a module. They are what BEM calls "modifiers" and what SMACSS calls "subclassed modules."

The class name for a variant is: base module name + two hyphens + kebab-cased variant name.

Example: Module foo could have a variant in-progress, so the class name would be foo--in-progress (assuming you are not prefixing module names with m-).

Multiple variants can be applied to a module simultaneously, if it makes sense. You can style these intersections when needed:

.msg { /* message module */ }
.msg--loading { color: gray; }
.msg--mine { color: blue; }
.msg--loading.msg--mine { color: slategray; }

Variants can, but do not necessarily, represent states. They can also transform the module as desired for placement elsewhere. Or they can simply change the appearance to grab more or less visual attention. The classic example is btn, a normal button, and btn--primary, a 'primary' button with strong contrast to the surrounding area due its purpose as the primary action on the screen.

Do apply multiple variant classes to an element as appropriate:

<input
  type="submit"
  class="btn btn--primary btn--disabled"
  value="Submit" />

<section role="alert" class="mdl mdl--alert mdl--open">
  <header class="mdl__header"> ... </header>
  <div class="mdl__body"> ... </div>
</section>

Do not add the base module class to selectors unnecessarily: Style .btn--primary, not .btn.btn--primary.

Layout Modules

One valid use of a module is to create one or more containers for laying out content (= other modules).

There is no special naming designation for layout modules. In fact, a module can handle its own layout as well as doing other things. You do not need to decide whether a module is a layout module or a regular module; it can be both. Or you can separate the concerns if that increases your re-usability.

You can use the wildcard direct child selector (> *) in layout modules, but it is even better to swap this for named parts.

.xyz { /* module that lays out its children */ }
.xyz > * { flex: 1; } /* Acceptable selector */
.xyz__child { flex: 1; } /* Even better selector! */

ZASM strongly supports responsive design, which is what we call it when the same page, with the same markup, effortly resizes and rearranges itself to fit screens of any size and shape. ZASM avoids simple "layout" classes that always do the same thing (e.g., apply two columns). Instead we empower modules to lay out their parts however they want. That will often mean using @media queries to switch to a compact, single-column layout on narrow screens. Or it can mean using flexbox-based responsive techniques. Or liberal use of calc(). Or all of the above. It's up to you!

Caution: Never write CSS with descendant and element name seletors like this for a layout module:

.xyz div { float: left; }

That's a great way to mess up just about every module nested within .xyz, and it’s a completely avoidable violation of ZASM rules.

Content-Oriented Modules

Sometimes you can't realistically apply class names to everything. For instance, the contents of an article probably include lots of headings and paragraphs that don't have class names on them. Or maybe you need to display content generated by a third-party system or a certain software library that has its own ideas about markup.

This is not a problem in ZASM. After all, we have taken care to keep our base styles minimal, and most of our styles only trigger off the specific class names we have invented usng ZASM naming rules.

Cases where you need to bulk-style content that may be using no class names or non-ZASM class names call for content-oriented modules.

These are just a special case of module that is focused on styling its children and descendants.

Imagine a "content" module named cnt.

<article class="cnt">
  <h1>Why My Dog Really Did Eat My Homework</h1>
  <p>Toby is not a bad dog. In fact, ... </p>
</article>
.cnt h1 { /* ... */ }
.cnt p { /* ... */ }

Notably content-oriented modules can easily be nested within other modules, but other modules cannot be nested within a content-oriented module without risking styles breaking.

For this reason, content-oriented modules should be relatively few, lest you ruin the composability and predictability of your ZASM-based stylesheet.

By the same logic, you should strive to use the direct child selector (>) whenever possible instead of the descendant selector (space), but this will often be impossible for a well-needed content-oriented module. For example, .cnt > p will work on our example HTML, but it will probably break once a piece of content appears that contains a p using a <section> or <div> tag!

Utilities

Utility classes should be defined sparingly. You may not even need or write a single utility class in a well-organized ZASM project.

Name them with a u- prefix and kebab-case (hyphenate) the rest.

Utility classes are standalone classes that achieve a narrow purpose. If you are tempted to make a "variant" or "part" of a utility, you should convert it to a module instead.

.u-clear {
  clear: both;
}

.u-small-caps {
  font-variant: small-caps;
}

.u-tabular-nums {
  font-variant-numeric: tabular-nums;
}
Don’t-Do’s
  • Never use ancestor or parent selectors to style a module differently based on where it appears. Create a variant instead.

  • Do not invent states (like .is-current). Instead, invent a variant.

  • Do not write layout classes; instead, define a module that has a layout purpose.

  • Do not use !important (unless it is necessary due to some horrible legacy code you can't jettison).

  • Do not write element-based selectors (except for the smallest number necessary in base styles). For example, do not style main or article. Instead, invent a module and apply it. It is acceptable to use ancestor selectors to style elements within a content-oriented module: .cnt p { margin 1.2em 0; } is a fine rule for styling paragraphs within your main content area .cnt.

Non-Styling Class Names

When elements will be targeted via JavaScript (e.g. with querySelector()), prefer a js- prefix: class="js-whatever".

For instrumententation of click tracking, use a consistent prefix such as track-: class="track-home-page-call-to-action".

For enabling automated QA testing, always use class names beginning with qa-: class="qa-home-page-call-to-action".

It is recommended to apply as many non-styling class names to the same element as needed, even if that element also uses one or more ZASM class names for styling purposes.

Cheat Sheet

For quick reference, enjoy the ZASM cheat sheet.

Thanks

This methodology is hugely inspired by SMACSS and BEM.

This document is open source. Issues and pull requests are welcome.

License

The Zero-Ambiguity Stylesheet Methodology (ZASM) by Alan J. Hogan is licensed under CC BY 4.0

https://alanhogan.com/code/css/zasm
Using “Deprecation” Correctly

Many people, including programmers and project managers, use the word “deprecation” incorrectly. This can lead to confusion and even unexpected downtime, so let’s all try to use the same definition.

What is deprecation?

The stellar Oxford English Dictionary defines the verb “deprecate” this way:

  1. express disapproval of: what I deprecate is persistent indulgence.
    • (be deprecated) (chiefly of a software feature) be usable but regarded as obsolete and best avoided, typically due to having been superseded: this feature is deprecated and will be removed in later versions | (as adjective deprecated) : avoid the deprecated <blink> element that causes text to flash on and off.

Deprecation is the moment, act, or expression of such disapproval, or as we mean it particularly in software, “the classification of a software feature as obsolete and best avoided” (again Oxford).

What is depreciation?

Sometimes deprecation and depreciation are confused (because they look nearly identical). The latter is more common in finance than technology, and it refers to the reduction in value of something over time.

What isn’t deprecation?

When a piece of software, a service, or a feature is actually taken offline, or reaches an end-of-life or end-of-support phase, this is variously called sunsetting or EOL, but it is definitely not deprecation. Deprecation should have come earlier (often months or years earlier), to warn customers and users of the impending EOL event.

An example of doing it right

In a recent blog post by Brent at Stitcher, one deprecation is described like this:

Dynamic properties are deprecated in PHP 8.2, and will throw an ErrorException in PHP 9.0.

This is a good example because it clearly separates the PHP version which marks the feature as best avoided from the future version in which the feature will no longer work at all.

https://alanhogan.com/conversations/deprecation
Upgrading AlanHogan.com to PHP 8.1

This website runs my own content management system. Previously I wrote about upgrading it from PHP 5.6 to PHP 7.

Now I have managed to upgrade it to PHP 8.1 as well.

How?

First, I upgraded my staging site to run PHP 8.1. It failed badly — but I checked the error logs. It became clear that language changes had resulted in code in some of the libraries I am using (open-source code) was no longer compatible.

One such library was ADOdb_lite, an abandoned project. But that was not a big deal. ADOdb_lite had the same API as the larger project ADOdb, which, luck would have it, is still maintained as I write this.

The other incompatibility was PHP Markdown, which is also still maintained. Thus, all I had to do was replace my version with the latest.

And that was it. My site was 100% functional on PHP 8.1!

Well, at least, as far as I could tell. A later look at my error logs showed that I was using an obsolete function in part of the code path of my popular online MIPS assembler and the corresponding MIPS simulator. The functionality of the obsolete function isn't needed in newer versions of PHP, so the fix was simple. I just added a check to see if the function exists before calling it.

That was all I needed to do. However, my error logs were still showing warnings about undefined variables and undefined array keys. These did not indicate bugs or incompatibilities (at least not necessarily), but nevertheless I added a few isset() calls, ensuring the variables and array keys I was accessing really did exist first. This, if nothing else, helped tidy up the error log, reducing noise.

I want to give a big shout-out to Michel Fortin, maintainer of PHP Markdown, for being a great maintainer. Same to Damien Regad and Mark Newnham of ADOdb, who took over from the similarly great John Lim. It's thanks to these open-source maintainers that I have been able to keep my aging website online over the years with minimal maintenance effort.

https://alanhogan.com/conversations/php/upgrading-to-php-8-1
Insert the Current Date with a Keyboard Shortcut

Here’s a useful Mac tip to insert the current date in your preferred format nearly anywhere. It does not require any third-party software that can potentially slow down your computer and cost money.

I’ll show you how to add two specific date formats, as well as one for the current time, with this technique. You can use any, all, or a customization that suits your purposes.

You can either download my zip file (recommended) or paste the code into Automator yourself.

With My Downloadable “Quick Actions”

Date and Time Quick Actions.zip

  1. Download this zip file. Unzip it. Place the contents in the correct location.

    • From a Finder window, just use the menu item Go > Go to folder…, enter “~/Library/Services” (no quotes), and hit Return.

    • Then drag the Workflow files you unzipped into the folder.

    • You will probably need to do something to get macOS to recognize these scripts. I recommend simply opening these scripts in Automator (by double-clicking each of them), then closing them. Or just log out of, and then back into, your Mac!

    • If this is successful, then in most applications, you should be able to view your new Quick Actions by clicking the current application name in the menu bar (top left of the screen) and then hovering over Services.

  2. Now let's make these actions available by keyboard shortcuts! Open System Settings > Keyboard > Shortcuts. Select “App Shortcuts” on the left. Select “All Applications” on the right. Click the Add button (which looks like a plus sign). For “Menu Title,” the exact name of the first Workflow you created; in my case it is “Insert Current Date as ISO”. Then set any shortcut you’d like; I’m rolling with “^⌥⌘M”. (This step works because your Quick Action workflow appears in the menu bar under [App Name] > Services all the time.) I recommend the shortcuts and names in the table below.

    Screen shot during this step Screen shot after this step

  3. Because sometimes the above shortcuts don’t take, for some odd reason, there is one more step for each shortcut. While we are in System Settings > Keyboard > Shortcuts, Click “Services” on the left, expand the ”Text” section if applicable, find your new date/time actions, and add a keyboard shortcut to each. (Don’t see your actions? Take a look at the notes under step 1.) Use the same keyboard shortcut for each action as you did in the previous step! One or the other of these mechanisms should work — at least most of the time.

    Screen shot after this step

  4. That’s it. Now in almost any context, you can invoke your keyboard shortcut to insert the current date in ISO format at your insertion point (where the blinking cursor is, or replacing any currently selected text).

My Date & Time Shortcuts Name Suggested Shortcut AppleScript Insert Full Current
Human-Readable Date ^⌥⌘D
tell (current date) to return date string
Insert Current
Date as ISO ^⌥⌘M
tell (current date) as «class isot» as string to return the contents of [text 1 thru 10] as text
Insert Current Time ^⌥⌘T
tell (current date) to return time string
Alternatively, Use Automator Yourself

Here is the manual method in case you don’t trust my downloadable zip.

  1. Open Automator.
  2. Create a new document of type “Quick Action”.
  3. Insert “Run AppleScript” item.
  4. Replace the text with one of the short scripts from the table above. (Safety tip: Ask a trusted programmer friend if the code looks safe.)
  5. Choose “Workflow receives no input in any application,” and check “Output replaces selected text.”
  6. Save it with an appropriate name (my suggestion is to use the corresponding value in the first column of the table above). It should default to the correct location, which is your user directory’s Library/Services file.
  7. Now follow the remaining steps from the downloadable method, starting with step 2.
What About iPhones?

Bonus tip: Do you miss this shortcut on your iPhone or other iOS device? There is an affordable app that provides similar functionality via a software keyboard: TimeStamp Keyboard.


Updated April 2024 with newer System Settings screen shots, no longer showing a mismatched keyboard shortcut. Thanks to reader Lynn for reporting the error. I also changed references from System Preferences to System Settings, reflecting changes in macOS since this article was originally published.

Thanks to CJK for a helpful answer on Stack Overflow that gave me most of what I needed here. Let me know if you liked this tip or have feedback on it.

https://alanhogan.com/tips/insert-current-date-with-keyboard-shortcut
Anticipated Iowa Poll Canceled Because Someone Neglected Accessibility

In a remarkable turn of events, and in the final days before the first Democratic primary election of the 2020 season, the results of a major presidential poll have been discarded and will not be released. Candidates and commentators were counting on the results of this poll for last-minute strategy and projection purposes.

An inconsistency had been discovered after at least one poll respondent said that their preferred candidate, Pete Buttigieg, was not included in a list of candidates read to them over the phone. This was unexpected, and poll results were invalidated over concerns that it was unclear how many times this error had occurred during polling operations.

Naturally this error drew concern and prompted some investigation as to its cause. The results were remarkable.

Reportedly, the poll worker had increased the font size on their computer, causing the final candidate’s name to be pushed off-screen and thus to be unread.

This is a dramatic example of an unfortunately commonly overlooked aspect of accessibility in software design: support of font size enlargement. Too often, interfaces are built to fit text at one size, with bounding boxes given hard-coded dimensions. When text sizes increase — or when that text is translated into a more long-winded language — this can lead to text overflowing those boxes. With nowhere to go, such text is often rendered invisible and unreadable.

While it is not clear from reports if this exact scenario was at play in the case of the wasted poll (perhaps the user missed a scroll bar?), it certainly is a common error, and this case provides a dramatic example of the costs of inaccessible interface design and development.

https://alanhogan.com/accessibility-error-sinks-poll