Skip to content

HTML and Visualforce

TODO(David)

Write this page. Stub created for structure only.

TODO

Visualforce page standards, LWC/Aura markup, escaping and XSS

Standards for markup, whether a developer writes it or a component generates it: accessibility, comments, Visualforce, CSS, SLDS 2, and XML.

Dynamically Generated HTML

TODO(David)

Find a new name for this heading. The current name is a placeholder.

The standards on this page are for the final HTML: the markup that reaches the browser, whether a developer wrote it or a Visualforce, Aura, or Lightning Web Component generated it.

The rules check what can be checked in the source, such as an alt attribute on a Visualforce image tag. The final HTML cannot be checked without additional tools, so some standards are left for those tools to enforce (How Accessibility Is Enforced).

Accessibility

Accessibility is a fundamental requirement: all markup conforms to WCAG 2.2 Level AA. The baseline, how it is enforced, and the United States and Australian laws that require it are set out on the Accessibility page.

Images

The standards for images, and for buttons and links that need a name, apply to every kind of markup, and they are on the General: Accessibility page.

Tables

A screen reader reads a data table one cell at a time, and can announce the headers of a cell only when the markup says which they are (WCAG 1.3.1, Level A).

  • A data table says what it holds, in a caption or in an aria-label, aria-labelledby, or aria-describedby attribute with text (technique H39). An apex:dataTable or an apex:pageBlockTable generates its caption from a caption facet (apex:dataTable). A table with none is reported at severity 4. Enforced by TableWithoutCaptionVf (Visualforce) and TableWithoutCaptionHtml (Lightning Web Components and Aura).
  • A data table marks its header cells with th (technique H51). A table with no th is reported at severity 4. Enforced by TableWithoutHeadersVf (Visualforce) and TableWithoutHeadersHtml (Lightning Web Components and Aura).
  • A header cell has a scope, or an id that cells name in their headers attribute (technique H63, technique H43). A th with neither is reported at severity 4. A th in a layout table is reported by the layout table rules, not by these. Enforced by TableHeaderWithoutScopeVf (Visualforce) and TableHeaderWithoutScopeHtml (Lightning Web Components and Aura).
  • A headers attribute names only the id of a th in the same table. A name that matches no header cell is reported at severity 4. Enforced by TableCellHeaderNotFoundVf (Visualforce) and TableCellHeaderNotFoundHtml (Lightning Web Components and Aura).
  • No table is used for layout. The HTML Standard does not allow a table as a layout aid (The table element): layout is done in CSS. A table with role="presentation" or role="none" is reported at severity 3. Enforced by LayoutTableVf (Visualforce) and LayoutTableHtml (Lightning Web Components and Aura).
  • A layout table carries no caption, th, summary, headers, or scope. That markup tells assistive technology that the table holds data, which fails WCAG 1.3.1 (failure F46). A layout table with any of them blocks the pull request (severity 2). Enforced by LayoutTableWithSemanticMarkupVf (Visualforce) and LayoutTableWithSemanticMarkupHtml (Lightning Web Components and Aura).

Form Groups, Frames, and Lists

A tag that only repeats or groups its content, such as apex:repeat, aura:iteration, or a template tag with for:each, may sit between a list and its items. A div that groups a dt and its dd may sit in a dl. An li at the root of a child component is not reported, because its list is in the parent. The root template of a Lightning web component, an aura:component, and an apex:component are treated alike. In a Visualforce page or an Aura application, an li with no list above it is reported.

Media

  • A video has a track of captions or subtitles (WCAG 1.2.2, Level A; technique H95). A track with no kind counts, because subtitles are the default (The track element). A video without one is reported at severity 3. Enforced by VideoWithoutCaptionsVf (Visualforce) and VideoWithoutCaptionsHtml (Lightning Web Components and Aura).
  • An object element holds fallback content, for a browser or a reader that cannot show the object (technique H53). An object with none is reported at severity 4. Enforced by ObjectWithoutAlternativeVf (Visualforce) and ObjectWithoutAlternativeHtml (Lightning Web Components and Aura).
  • No image is a server-side image map. The ismap attribute sends the server the coordinates of a click, so the map cannot be worked from the keyboard (WCAG 2.1.1, Level A). An ismap attribute blocks the pull request (severity 2), whatever its value, as an image-map link with no name does: on an img, the attribute being there turns the map on (Boolean attributes). Only ismap="false" on an apex:image is not reported. Enforced by ServerSideImageMapVf (Visualforce) and ServerSideImageMapHtml (Lightning Web Components and Aura).

Keyboard

  • Every function that a mouse reaches, a keyboard reaches too (WCAG 2.1.1, Level A). An onmouseover has an onfocus, and an onmouseout has an onblur (technique SCR2). An onclick has an onkeydown, onkeyup, or onkeypress (technique SCR20). A mouse handler with no keyboard equivalent is reported at severity 3. Enforced by MouseEventWithoutKeyboardEventVf (Visualforce) and MouseEventWithoutKeyboardEventHtml (Lightning Web Components and Aura).
  • A browser fires click from the keyboard on a link, an image-map area, a button, a form control, and a summary, so their onclick needs no key handler. The same is true of a Visualforce component that renders one of them, such as apex:commandButton, apex:commandLink, apex:outputLink, or an input component, and of a Lightning component, which handles its own keyboard behaviour. An apex:outputPanel renders a div or a span, so its onclick needs a key handler.

Landmarks

  • When a page has two or more landmarks with the same role, each has a label that is different from the others, in an aria-label or aria-labelledby attribute (technique ARIA11, for WCAG 1.3.1 and WCAG 2.4.1, Level A). A screen reader lists the landmarks of a page, and a user moves from one to the next. With two navigation landmarks and no labels, the user cannot tell the main menu from the footer links. A landmark with no label, or with the label of another landmark of its role, is reported at severity 4. Enforced by RepeatedLandmarkWithoutUniqueLabelVf (Visualforce) and RepeatedLandmarkWithoutUniqueLabelHtml (Lightning Web Components and Aura).
  • The landmarks are nav (navigation), aside (complementary), main, search, a form with a label, and any element whose role names a landmark role, such as role="navigation" or role="banner". A form with no label is not a landmark, so an apex:form is not counted. A header, footer, or section is counted only by its role, because it is a landmark only in some places.
  • An empty label counts as no label. Two labels are the same when their text is the same, without regard to case and spaces. A merge expression or a bound property counts as a unique label, because its value is not known before the page runs.
  • The rules compare the landmarks of one file. When the landmarks of two components show on one page, reviewers check that their labels are different.

TODO

Labels and ARIA, SLDS semantics

Source (Well-Architected, Compliant / Accessibility; see standards-sources memory)

Colour and contrast (Well-Architected cites WCAG 2.0, but the site's normative baseline is WCAG 2.2 AA per David's 2026-07-12 ruling in standards-sources Standing decisions), never colour alone to convey meaning, visible keyboard focus at all times, form usability (visible labels, helpful errors, progress indication), test with multiple input devices, Salesforce ACRs as the baseline conformance documents

Credentials in Markup

A credential in the markup of a component is in the repository, as one in a class is (Security).

  • No credential is the default value of an attribute. An aura:attribute in an Aura component, or an apex:attribute in a Visualforce component, whose name says that it holds a credential, such as apiKey or password, and that has a default value blocks the pull request (severity 2). Enforced by AvoidHardCodedCredentialsInAura (Aura) and AvoidHardcodedSecretsInVFAttrs (Visualforce).

Both rules read the name and not the value. They also report a harmless default under such a name, and a custom component (c:) that is given an attribute with such a name, whatever its value. The owning team accepts a false alarm of that kind (Fixing a Blocking Violation).

Comments

The General: Comments standards apply to markup as they do to code. These are particular to markup.

  • TODO, TO-DO, FIXME, FIX-ME, or HACK in a markup comment (<!-- ... -->), as the first word of one of its lines or with a colon after it, in a Visualforce page or component, a Lightning Web Component template, or an Aura component, is reported at severity 5. Enforced by NoUnresolvedTodosMarkup.
  • A Visualforce page or component starts with a comment that says what it is for, before its first tag; blank lines may come first. A page or component that does not is reported at severity 4. The rule checks only that a comment comes first, and reviewers check what it says. Enforced by VisualforceHeaderComment.
  • The Salesforce Documenter (SFDoc) extension writes its default author, ChangeMeIn@UserSettingsUnder.SFDoc, into the header of a Visualforce page or component, a Lightning Web Component template, and an Aura component. That string in any of those files blocks the pull request (severity 2). Enforced by NoSfdocDefaultAuthor.

Lightning Frameworks HTML

Standards that apply to the HTML of Lightning Web Component templates and Aura markup. The standards that hold for all markup, Visualforce included, are in sections of their own on this page.

Readability

Code Style

TODO

Stub. Standards that make HTML easier to read and that may not change what it does: stylistic choices (Content item 142).

Formatting

TODO

Stub. The Prettier and purely cosmetic standards of HTML, such as tabs, bracket spacing, and the space before a self-closing slash (Content item 142).

Whitespace Sensitivity

TODO

Stub. Where white space between two tags changes what a user sees in a Lightning Web Component template or Aura markup, and the Prettier setting that these standards choose for it: htmlWhitespaceSensitivity (Prettier, What an Organisation Customises, "White space in Visualforce markup") (Content item 142).

Required Formatting

TODO

Stub. Formatting that the platform requires, as against formatting that these standards choose (Content item 142).

Error Prevention

TODO

Stub. Code choices in HTML that work today and could cause a problem later (Content item 142).

  • No attribute in a Lightning Web Component template, in Aura markup, or in a plain HTML file holds a javascript: URL, as in Visualforce (Resources and Links). In a component, the Content Security Policy of the platform blocks an inline script, so such a link or handler does nothing (Scripts and Resources). The event is handled in the component's JavaScript, and a link has a real address. A javascript: URL blocks the pull request (severity 2), and javascript:void(0) is allowed through the allowedPattern property. Enforced by AvoidJavaScriptUrlHtmlLwcAura.
  • A Lightning Web Component template or Aura markup does not hold the absolute URL of a Salesforce org, as a Visualforce page does not (Resources and Links). A link is relative, or comes from a bound property that the component's JavaScript builds. The same allowedSubdomains property names the Salesforce websites that may be written out. A URL of a Salesforce org is reported at severity 3, in attribute values and in text. Enforced by AvoidAbsoluteSalesforceUrlHtml.

Visualforce

Standards that apply to Visualforce pages and components.

Standard Components

A Visualforce page is built from the standard Visualforce components, such as apex:pageBlockSection, wherever one does the job (Standard Visualforce Component Reference). This is Built-In First applied to Visualforce.

  • A standard component is used wherever there is one for the purpose. Plain HTML is mixed in as needed, where no component fits.
  • Fields are laid out with the standard layout components wherever possible: apex:pageBlock, apex:pageBlockSection, apex:inputField, and apex:outputField. They carry Salesforce's own styling, so a page built from them takes on new Salesforce styles with no change to its markup. With lightningStylesheets="true" on apex:page, for example, they are styled to match Lightning Experience (Style Existing Visualforce Pages with Lightning Experience Stylesheets).
  • JavaScript is used sparingly, because many components have it built in. apex:actionSupport, for example, refreshes part of a page when an event occurs, with no script written for it (apex:actionSupport).
  • A page or component has one apex:form, which Salesforce calls a best practice (apex:form). A part of the page that posts on its own uses an apex:actionRegion inside the one form. A second form is reported at severity 3. Enforced by AvoidMultipleFormsVf.
  • A page does not frame another page with apex:iframe or iframe. It includes a component, or links to the page. Visualforce pages and custom iframes are not supported in Lightning Experience on iPad Safari (Developing Salesforce Apps with Visualforce). A page that embeds a third-party site has no other way, and its owning team accepts the report. A frame is reported at severity 4. Enforced by AvoidIframeVf.

No rule checks these standards. Reviewers do.

External Scripts and Stylesheets

A Visualforce page loads its JavaScript and its CSS from static resources, never from another site. A script fetched from another host runs with the page's access to the user's session and data, and either kind of file can change without a review or a deployment.

  • A script comes from a static resource, in a script tag and in apex:includeScript alike. One loaded from anywhere else blocks the pull request (severity 2). Enforced by LoadJavaScriptHtmlScript and LoadJavaScriptIncludeScript.
  • A stylesheet comes from a static resource, in a link tag and in apex:stylesheet alike. One loaded from anywhere else blocks the pull request (severity 2). Enforced by LoadCSSLinkHref and LoadCSSApexStylesheet.
  • Nothing loads from polyfill.io. After the domain changed hands in 2024, the script it served was altered to send visitors to malicious sites (Sansec). A reference to it in a Visualforce page or component, in Lightning Web Component or Aura markup, or in a metadata file such as a CSP Trusted Site, blocks the pull request (severity 2). Enforced by NoPolyfillIo.

A reference through $Resource, with or without URLFOR, is the best practice. A script written in the page itself is also allowed.

  • A Visualforce page or component should not use Lightning Components for Visualforce: apex:includeLightning, lightning.out.js, or $Lightning.use. It is built on Lightning Out (beta), which requires Lightning Locker (Use Components in Visualforce Pages, Lightning Web Security). A page or component that uses it is reported and does not block (severity 3), and an organisation may allow it with a suppression and a documented reason. Enforced by NoLightningOutInVisualforce.
  • A static resource is referenced by name through $Resource, with URLFOR for a file inside an archive, and not by a /resource/ path (Using Static Resources). A hard-coded /resource/ path is reported at severity 3. Enforced by AvoidHardcodedResourcePathVf.
  • A link to another Visualforce page is written with $Page, and not as an /apex/ path ($Page). A hard-coded /apex/ link is reported at severity 3. Enforced by AvoidHardcodedPageLinkVf.
  • A link does not point to a hard-coded record Id, such as /001…. A record Id belongs to one org, so the link is wrong in every sandbox and every other org, the same as in Apex. A link to a record is built with URLFOR and $Action, as the Visualforce Developer Guide advises (Understanding the Salesforce Mobile App Container), or from a field of the record. A link to a hard-coded record Id blocks the pull request (severity 1). A link built from an expression, such as /{!acc.Id}, is not reported. Enforced by AvoidHardcodedIdVf.
  • A page does not hold the absolute URL of a Salesforce org, the same as Apex. Every sandbox and every production org has a host name of its own, so the URL of one org is wrong in every other org, and it breaks when the org's My Domain name changes. A link within the org is relative, or built from $Site, URLFOR, or $Page. A Salesforce website that is the same for every org, such as Salesforce Help, may be written out, and the rule's allowedSubdomains property names those websites. A URL of a Salesforce org is reported at severity 3, in attribute values, page text, script text, and strings inside an expression. Enforced by AvoidAbsoluteSalesforceUrlVf.
  • No attribute holds a javascript: URL. MDN warns that a javascript: URL may lead to the execution of arbitrary code, as eval() does (javascript: URLs). The event is handled in a script, and a link has a real address. A javascript: URL blocks the pull request (severity 2). javascript:void(0), a link that does nothing, is allowed: the rule's allowedPattern property holds the values that may be written, and each organisation sets its own. Enforced by AvoidJavaScriptUrlVf. The same standard applies in Lightning markup (Error Prevention), and in JavaScript files at the same severity (JavaScript).

Console Statements

A script block in a Visualforce page or component is JavaScript, and it follows the JavaScript standards. A console statement there can be read by anyone who opens the browser's developer tools, as one in a component can (Console and Debugger Statements).

  • No console statement in a Visualforce page or component. One blocks the pull request (severity 2). Enforced by NoConsoleInVisualforce.

Script Blocks

  • A script block does not hold the text of another script tag. The HTML parser gives <script a special meaning inside a script block, so the block can end somewhere other than where its author meant (Restrictions for contents of script elements). A script that has to write a script tag escapes it as \x3Cscript, or the page loads the other script with apex:includeScript. A script block that holds a script tag is reported at severity 3. Enforced by NestedScriptElement.

A hard-coded path is not adjusted for the path prefix of an Experience Cloud site, and the two global variables are.

Naming

The naming standards for Visualforce, for pages, components, and controllers, are on the General: Naming page, with the naming standards of the other languages.

Readability

Code Style

TODO

Stub. Standards that make Visualforce easier to read and that may not change what it does: stylistic choices (Content item 142).

Formatting
  • Formatting is applied by Prettier, from the repository's Prettier configuration file, on save and again when a commit is made (Section 4). It is not a matter for review. A page or component that is not formatted blocks the pull request (Appendix A.2.1).
  • A Visualforce page or component is formatted with Prettier's html parser, as Salesforce's own Prettier configuration sets it (Prettier Code Formatter).
  • Markup is indented with tabs, and a line is at most 180 characters wide (Version Control: Prettier).
  • The > that closes a start tag stays on the line of its last attribute (bracketSameLine is true), and attributes share a line until the line is full (singleAttributePerLine is false).
  • A self-closing tag has a space before its slash: <apex:outputText value="{!name}" />. Prettier always writes it so in markup.
  • Attribute values are in double quotes (singleQuote is false).
  • A page or component retrieved from an org is formatted again before it is committed. When it saves a page, Salesforce moves the first attribute of each apex: tag back to the line of the tag name, joins an end tag that Prettier split over two lines, and removes the final newline. It keeps tabs, spaces, and line endings as they were.
  • Visualforce is formatted with Prettier only, and not with the Visualforce formatter of the Salesforce Extensions for Visual Studio Code, which changes white space and how a page renders.
Whitespace Sensitivity

Visualforce removes the white space just before an apex: start tag or end tag when it renders a page. So Read the <apex:outputLink value="{!url}">guide</apex:outputLink> shows "Read theguide", and two components with a space between them show with none. White space after an apex: tag, and white space next to a plain HTML tag, is kept.

  • htmlWhitespaceSensitivity is "css". Prettier then breaks a line only where white space already is, or inside a tag, and the page that a user sees does not change. With "ignore", Prettier puts line breaks between inline elements, and the page shows a space where there was none (HTML Whitespace Sensitivity).
  • A space that must show before an apex: tag is written as the merge expression {!' '}: Read the{!' '}<apex:outputLink value="{!url}">guide</apex:outputLink>. Visualforce writes it as a space, and Prettier never breaks a line inside it. A space at the end of a plain HTML element's text (<span>Read the </span>), or at the end of the value of the component before the tag, is also kept. The character reference &#32; is removed like a typed space, and &nbsp; shows but stops the line from wrapping there.

No rule checks this. Reviewers do.

Required Formatting

TODO

Stub. Formatting that the platform requires, as against formatting that these standards choose (Content item 142).

Error Prevention

TODO

Stub. Code choices in Visualforce that work today and could cause a problem later (Content item 142).

  • A merge expression in a JavaScript string has a string of its own. Other text is joined to it with +.

The problem: a string with a merge expression and escaped quotes, such as '{!JSENCODE("Tom")} isn\'t the owner\'s or the team\'s'. Prettier changes the string to double quotes, because that needs fewer backslashes. It also puts a backslash before each double quote in the merge expression. Salesforce cannot read the merge expression, and it does not save the page.

The prevention: '{!JSENCODE("Tom")}' + " isn't the owner's or the team's". Prettier does not change this.

No rule checks this. Reviewers do.

Security

A Visualforce page runs in the user's Salesforce session, with the user's access to data. A flaw in the page gives that access to whoever can exploit it. Two attacks matter most for a Visualforce page: cross-site scripting and cross-site request forgery. Salesforce defends against both by default, and a page that goes around a default loses that defence. The three standards below keep the defaults in place.

Cross-Site Scripting

In a cross-site scripting (XSS) attack, text that an attacker controls reaches a page and runs there as script. The script runs as the user who opened the page: it can read every record that the user can see, change records, and send the user's session to the attacker, who can then act as the user (Cross Site Scripting (XSS)). The attacker's text can come from a URL parameter, from a field that another user filled in, or from any record the page shows. One flaw on one page is enough: when an administrator opens the page, the script has the administrator's access.

The usual route into a Visualforce page is a merge expression, such as {!accountName}, that is written out unescaped. Visualforce encodes a merge expression for HTML wherever it writes one into the text of the page, so that <script> arrives as text and not as a tag. That encoding is right for the text of the page and wrong for other places. Inside a script, an event handler, a link, or a style, a value needs the encoding of that place, and escape="false" turns the encoding off altogether (Unescaped Output and Formulas in Visualforce Pages). A value that is safe today, such as a number from the controller, is encoded too: when the controller later reads it from a URL parameter, the page is safe without a change.

  • A merge expression is always encoded for the place it is written. Text with escape="false" is encoded with HTMLENCODE; a merge expression in a script or in an event handler attribute is encoded with JSENCODE (JSINHTMLENCODE also passes); and a merge expression that starts a link is encoded with URLENCODE. A merge expression without the encoding its place needs blocks the pull request (severity 2). Enforced by VfUnescapeEl.
  • A merge expression in a style element is encoded: with URLENCODE or JSINHTMLENCODE inside a url(), and with an encoding function such as HTMLENCODE anywhere else in the style. Salesforce advises against any merge expression in a style element whose value the controller has not checked against an allowlist (Secure Coding Cross Site Scripting). An unencoded merge expression in a style element blocks the pull request (severity 2). Enforced by VfHtmlStyleTagXss.

Cross-Site Request Forgery

In a cross-site request forgery (CSRF) attack, a page on another site makes the user's browser send a request to Salesforce. The browser sends the user's session with the request, so Salesforce runs it as the user, who never meant to send it (Cross-Site Request Forgery (CSRF)). A link in an email or an image on a forum page is enough to send it.

Visualforce puts a random token in every form that it renders, and checks the token when the form is submitted, so a request from another site cannot submit a Visualforce form. The action attribute of apex:page is different: its method runs when the page loads, before any form, and no token is checked. An attacker who sends the user to the page, with the URL parameters that the attacker chooses, runs that method as the user. If the method deletes or changes a record, the attacker decides which one.

  • A page changes no data when it loads. The action attribute of apex:page does not call a method that inserts, updates, or deletes a record, or that sends anything out of the org. A change waits until the user submits a form, or confirms on a page of its own. An action attribute on apex:page blocks the pull request (severity 2). The rule cannot see what the method does, so it reports every action attribute; the owning team accepts one whose method only reads, such as a redirect (Fixing a Blocking Violation). Enforced by VfCsrf. The constructor of the page's controller runs when the page loads too, and the Apex standards cover it (Apex: Cross-Site Request Forgery).

Session Ids

A session Id is a credential: whoever holds it can act as the user until the session ends (Apex: Session Ids).

  • A Visualforce page does not read the session Id, in a script or in a merge expression. A call to Salesforce from a page goes through the page's controller, a Named Credential, or Visualforce remoting. GETSESSIONID() and $Api.Session_ID each block the pull request (severity 2) until they are removed or a reviewer accepts them. Enforced by AvoidUnauthorizedGetSessionIdInVisualforce and AvoidUnauthorizedApiSessionIdInVisualforce.

CSS

Standards for the stylesheets of Lightning Web Components, Aura components, and Visualforce pages.

  • Styling should live in a stylesheet, not in the page. In Visualforce, a style element and a style, labelStyle, or html-style attribute are inline styles; in a Lightning template, a style attribute is. An inline style is reported and does not block (severity 3). Enforced by AvoidInlineStyles in Lightning templates and by AvoidInlineStylesInVisualforce in Visualforce pages and components.
  • CSS is formatted by Prettier, like the code beside it (Formatting).
  • A focus outline is never removed without an equally visible replacement. outline: none leaves a keyboard user with no sign of where they are, and fails WCAG 2.4.7, a Level AA criterion. No rule checks this today. It is a blocking standard: a reviewer rejects a pull request that breaks it.
  • Text contrasts with its background by at least 4.5 to 1, or 3 to 1 for large text (WCAG 1.4.3, Level AA), and colour is never the only way that information is shown (WCAG 1.4.1, Level A). Contrast cannot be read from a stylesheet alone, so it is checked in the rendered page by the accessibility tests (How Accessibility Is Enforced).

Valid CSS

A browser drops a declaration it does not understand, without an error, so a mistake in a stylesheet fails silently.

  • Every property, unit, colour, selector, pseudo-class, pseudo-element, media feature, and at-rule is one that CSS defines.
  • A linear-gradient direction is a valid one, and the operands of calc have a space on each side of + and -.
  • A string does not run across a line break, and a comment is written /* */: CSS has no // comment.

No Dead or Duplicated CSS

  • No selector, property, @import, or font name is written twice, and no shorthand property overrides a longhand one written before it.
  • No stylesheet is empty, and none carries an empty comment or a stray semicolon.
  • !important is not used inside @keyframes, where browsers ignore the declaration.
  • A font-family list ends with a generic family, such as sans-serif, so that the text always has a fallback.

No rule in this reference checks the standards under these two headings. A reviewer does.

Readability

Code Style

TODO

Stub. Standards that make CSS easier to read and that may not change what it does: stylistic choices (Content item 142).

Formatting

TODO

Stub. The Prettier and purely cosmetic standards of CSS, such as tabs, bracket spacing, and the space before a self-closing slash (Content item 142).

Required Formatting

TODO

Stub. Formatting that the platform requires, as against formatting that these standards choose. CSS is not sensitive to white space, with one exception to check: + and - inside calc() need white space on both sides (Content item 142).

Error Prevention

TODO

Stub. Code choices in CSS that work today and could cause a problem later (Content item 142).

SLDS 2

Standards for SLDS 2 in Lightning Web Components and Aura components: the styling hooks of their stylesheets and the SLDS classes of their markup.

About SLDS 2

The Salesforce Lightning Design System (SLDS) is Salesforce's design system for Lightning Experience: its CSS framework, its component blueprints, and its guidelines. Salesforce introduced Salesforce Lightning Design System 2 (SLDS 2) in Spring '25, and now calls the original version SLDS 1 (Compare Salesforce Lightning Design System Versions).

SLDS 2 separates the structure of a component from its visual design. The visual design comes from styling hooks: CSS custom properties, such as --slds-g-color-border-1, whose values a theme sets. An org uses SLDS 2 when it chooses the Salesforce Cosmos theme; the default theme of SLDS 1 is Lightning Blue. Both versions use the same component blueprints, the markup and classes of each SLDS component.

A component that takes its colours, spacing, and type sizes from styling hooks follows the theme of the org, in SLDS 1 and in SLDS 2. A component that hard-codes them, or that uses the design tokens of SLDS 1, does not: SLDS 2 themes do not support design tokens. The standards in this section keep a component ready for SLDS 2. Salesforce does not yet support component styling hooks, whose names start with --slds-c-, in SLDS 2, and recommends that components that use them stay on SLDS 1 for now.

Every rule in this section is a rule of Salesforce's SLDS linter, @salesforce-ux/eslint-plugin-slds. Code Analyzer bundles that package, so there is nothing to install: the rules run wherever Code Analyzer runs. They check stylesheets (.css and .scss) and the markup of Lightning Web Components and Aura components (.html and .cmp). Code Analyzer does not give them Visualforce pages.

Styling Hooks

  • Colours, spacing, and type sizes come from SLDS 2 styling hooks, not from hard-coded values. A hook follows the org's theme and carries Salesforce's own accessible colour choices. A hard-coded value is reported at severity 4. Enforced by @salesforce-ux/slds/no-hardcoded-values-slds2.
  • A styling hook has its current name. A legacy --sds- hook is replaced by the --slds- hook of the same name: var(--slds-g-color-border-1, #e5e5e5), not var(--sds-g-color-border-1, #e5e5e5). A component styling hook, which only SLDS 1 supports for now (About SLDS 2), follows the current naming convention too: --slds-c-button-spacing-block-start, not --slds-c-button-spacing-blockstart. A legacy --sds- hook is reported at severity 4, and so is a component hook with an old name. Enforced by @salesforce-ux/slds/enforce-sds-to-slds-hooks and @salesforce-ux/slds/enforce-component-hook-naming-convention.
  • A deprecated --lwc- design token is replaced by the SLDS 2 styling hook that matches it, and the token stays as the fallback value: var(--slds-g-color-neutral-base-90, var(--lwc-colorBorder)), not var(--lwc-colorBorder). The order is never reversed: an --slds- hook is not the fallback value of an --lwc- token. A deprecated token is reported at severity 3, and an --slds- hook that is the fallback value of a token at severity 4. Enforced by @salesforce-ux/slds/lwc-token-to-slds-hook and @salesforce-ux/slds/no-sldshook-fallback-for-lwctoken.
  • No styling hook is used that SLDS 2 does not have. A hook that SLDS 2 removed, such as --slds-g-color-border-base-2, has no direct replacement: it is removed, or replaced with a hook that has a similar effect. A hook that SLDS 2 does not have is reported at severity 3. Enforced by @salesforce-ux/slds/no-unsupported-hooks-slds2.

Safe Use of Styling Hooks

  • Every SLDS styling hook in a var() has a fallback value, such as var(--slds-g-color-border-1, #e5e5e5). Styling hooks are not available in every Salesforce environment, and the fallback value is what the component shows where they are not. The fallback value may be a static value. A hook with no fallback value is reported at severity 3. Enforced by @salesforce-ux/slds/no-slds-var-without-fallback.
  • A custom styling hook has a name in the component's own namespace, such as --myapp-gap, never in the --slds- or --sds- namespace, which SLDS keeps for its own hooks. A name in those namespaces that SLDS 2 does not define is reported at severity 4, whether it is set or used in var(), so a misspelt or removed SLDS hook is reported too. Enforced by @salesforce-ux/slds/no-slds-namespace-for-custom-hooks.
  • No stylesheet sets or uses a private SLDS styling hook, one whose name starts with --_slds- or --slds-s-. Salesforce keeps those hooks for its own use. A stylesheet that sets a --_slds- hook is reported at severity 2. Enforced by @salesforce-ux/slds/no-slds-private-var. Salesforce's description of the rule names both prefixes, but a test of version 1.2.1 of the linter, the version that Code Analyzer 5.16.0 bundles, showed that the rule reports only a --_slds- hook that a stylesheet sets.

    How the private hook rule was tested, and the results

    Code Analyzer 5.16.0 scanned the stylesheet of a Lightning web component with the rules of the SLDS linter. The stylesheet held one case of each kind.

    Case Example Reported
    A --_slds- hook that the stylesheet sets --_slds-c-button-color: #0176d3; Yes
    A --_slds- hook used in var() color: var(--_slds-c-button-color, #0176d3); No
    A --slds-s- hook that the stylesheet sets --slds-s-button-color: #0176d3; No
    A --slds-s- hook used in var() color: var(--slds-s-button-color, #0176d3); No

SLDS Classes

  • An SLDS class is never overridden in a stylesheet. A component that needs a different look gets a class of its own, in its own namespace, such as myapp-button, beside the SLDS class. An override of an SLDS class is reported at severity 4. Enforced by @salesforce-ux/slds/no-slds-class-overrides.
  • No class is used that SLDS 2 does not have, such as slds-action-overflow_touch. It is replaced by a class that SLDS 2 supports. A class that SLDS 2 does not have is reported at severity 3, in markup and in stylesheets. Enforced by @salesforce-ux/slds/no-deprecated-classes-slds2.
  • An SLDS class name follows the BEM (block, element, modifier) convention of SLDS 2. A block is a component, such as slds-button or slds-modal. An element is a part of a block, after two underscores, such as slds-modal__container or slds-modal__close. A modifier is a variant of a block, after a single underscore, such as slds-button_brand or slds-text-heading_small. The double dash that SLDS once used for a modifier is retired: slds-button--brand is now slds-button_brand, slds-text-heading--small is now slds-text-heading_small, and slds-tabs--default__item, an element of a modified block, is now slds-tabs_default__item. A class name with the double dash is reported at severity 4. Enforced by @salesforce-ux/slds/enforce-bem-usage.

Modal Close Button

  • The close button of a modal, the element with the class slds-modal__close, follows the SLDS modal blueprint. It does not have the class slds-button_icon-inverse. A lightning-button-icon close button has the classes slds-button and slds-button_icon, and the variant of its icon is bare, not bare-inverse; so is the variant of a lightning-icon inside a close <button>. A close button that does not follow the blueprint is reported at severity 3. Enforced by @salesforce-ux/slds/modal-close-button-issue.

Suppression Comments

  • No finding of the SLDS linter is hidden with one of its suppression comments, @sldsValidatorIgnore, @sldsValidatorIgnoreNextLine, or @sldsValidatorAllow, without an approving review from the owning team, as for every other suppression (Comments). A suppression comment of the SLDS linter blocks the pull request (severity 2) until the finding is fixed or the owning team approves the suppression. Enforced by @salesforce-ux/slds/reduce-annotations.

XML

Standards for the layout of XML files, the metadata files of Salesforce included. The Metadata Standards pages hold what the metadata says.

Formatting

Prettier formats XML with @prettier/plugin-xml, and the configuration of these standards sets each of the cosmetic standards below (Prettier).

  • White space inside an element is preserved. Prettier indents the elements and leaves the text inside an element, such as a description or a formula, exactly as it is, so that it never changes the text of an element (xmlWhitespaceSensitivity is "preserve").
  • Most XML files are indented with tabs, as code is. A Salesforce metadata file is the exception: it is indented with spaces, not tabs. A -meta.xml file and a manifest are indented with 4 spaces, as Salesforce writes them, so that a formatted file and a retrieved file differ in as few lines as possible.
  • An empty element in a metadata file has no space before its closing slash, as Salesforce writes it (xmlSelfClosingSpace is false). Any other XML file has the space.

Duplicated Markup

  • No block of markup is copied from one page or component to another. A duplicated block is a component that both use, so that a change is made once. A block of HTML or Visualforce markup that appears more than once across the repository is reported at severity 5, for the developer's information (Refactoring Duplicates). Enforced by DetectCopyPasteForHtml and DetectCopyPasteForVisualforce.

Rules

The rules that this page mentions. The Standard column gives each rule's standard in one line, and the Section column links to the section that mentions the rule; "Not yet written" marks a rule of this page that no section mentions yet. Appendix F.6 holds the full cross-reference.

Rule Kind Severity Standard Section
AvoidHardcodedIdVf Custom PMD XPath 1 A Visualforce link does not point to a hard-coded record Id Resources and Links
@salesforce-ux/slds/no-slds-private-var Stock Code Analyzer ESLint 2 No private SLDS styling hook, one whose name starts with --_slds- or --slds-s- Safe Use of Styling Hooks
@salesforce-ux/slds/reduce-annotations Stock Code Analyzer ESLint 2 No SLDS linter suppression without an approving review from the owning team Suppression Comments
AvoidHardCodedCredentialsInAura Stock Code Analyzer PMD 2 No credential as the default value of an aura:attribute Credentials in Markup
AvoidHardcodedSecretsInVFAttrs Stock Code Analyzer PMD 2 No credential as the default value of an apex:attribute Credentials in Markup
AvoidJavaScriptUrlHtmlLwcAura Custom PMD XPath 2 No attribute in Lightning markup or plain HTML holds a javascript: URL, except javascript:void(0) Error Prevention
AvoidJavaScriptUrlVf Custom PMD XPath 2 No attribute in Visualforce holds a javascript: URL, except javascript:void(0) Resources and Links
AvoidUnauthorizedApiSessionIdInVisualforce Stock Code Analyzer PMD 2 A Visualforce page does not read $Api.Session_ID Session Ids
AvoidUnauthorizedGetSessionIdInVisualforce Stock Code Analyzer PMD 2 A Visualforce page does not call GETSESSIONID() Session Ids
LayoutTableWithSemanticMarkupHtml Custom PMD XPath 2 A layout table carries no caption, th, summary, headers, or scope Notes on this rule. Tables
LayoutTableWithSemanticMarkupVf Custom PMD XPath 2 A layout table in a Visualforce page carries no caption, th, summary, headers, or scope Notes on this rule. Tables
LoadCSSApexStylesheet Stock Code Analyzer PMD 2 An apex:stylesheet loads a stylesheet from a static resource External Scripts and Stylesheets
LoadCSSLinkHref Stock Code Analyzer PMD 2 A link tag in a Visualforce page loads a stylesheet from a static resource External Scripts and Stylesheets
LoadJavaScriptHtmlScript Stock Code Analyzer PMD 2 A script tag in a Visualforce page loads JavaScript from a static resource External Scripts and Stylesheets
LoadJavaScriptIncludeScript Stock Code Analyzer PMD 2 An apex:includeScript loads JavaScript from a static resource External Scripts and Stylesheets
NoConsoleInVisualforce Custom Code Analyzer Regex 2 No console statement in a Visualforce page or component Console Statements
NoPolyfillIo Custom Code Analyzer Regex 2 Nothing loads from polyfill.io External Scripts and Stylesheets
NoSfdocDefaultAuthor Custom Code Analyzer Regex 2 The author is a person or a team, never a tool default such as ChangeMeIn@UserSettingsUnder.SFDoc Comments
ServerSideImageMapHtml Custom PMD XPath 2 No image is a server-side image map Media
ServerSideImageMapVf Custom PMD XPath 2 No image in a Visualforce page is a server-side image map Media
VfCsrf Stock PMD 2 A page changes no data when it loads; the action of apex:page calls no method that writes Cross-Site Request Forgery
VfHtmlStyleTagXss Stock PMD 2 A merge expression in a style element is encoded Cross-Site Scripting
VfUnescapeEl Stock PMD 2 A merge expression is always encoded for the place it is written Cross-Site Scripting
@salesforce-ux/slds/lwc-token-to-slds-hook Stock Code Analyzer ESLint 3 No deprecated --lwc- design token; use the matching SLDS 2 styling hook, with the token as its fallback value Styling Hooks
@salesforce-ux/slds/modal-close-button-issue Stock Code Analyzer ESLint 3 A modal close button follows the SLDS modal blueprint Modal Close Button
@salesforce-ux/slds/no-deprecated-classes-slds2 Stock Code Analyzer ESLint 3 No class that SLDS 2 does not have SLDS Classes
@salesforce-ux/slds/no-slds-var-without-fallback Stock Code Analyzer ESLint 3 Every SLDS styling hook in var() has a fallback value Safe Use of Styling Hooks
@salesforce-ux/slds/no-unsupported-hooks-slds2 Stock Code Analyzer ESLint 3 No styling hook that SLDS 2 does not have Styling Hooks
AvoidAbsoluteSalesforceUrlHtml Custom PMD XPath 3 Lightning markup holds no absolute URL of a Salesforce org Error Prevention
AvoidAbsoluteSalesforceUrlVf Custom PMD XPath 3 A Visualforce page holds no absolute URL of a Salesforce org Resources and Links
AvoidHardcodedPageLinkVf Custom PMD XPath 3 Link to a Visualforce page with $Page, not an /apex/ path Notes on this rule. Resources and Links
AvoidHardcodedResourcePathVf Custom PMD XPath 3 Reference a static resource through $Resource, not a /resource/ path Notes on this rule. Resources and Links
AvoidInlineStyles Stock PMD 3 Styling lives in a stylesheet, not in an inline style attribute CSS
AvoidInlineStylesInVisualforce Custom PMD XPath 3 No style element, and no style, labelStyle, or html-style attribute, in a Visualforce page or component CSS
AvoidMultipleFormsVf Custom PMD XPath 3 A Visualforce page or component has one apex:form Standard Components
LayoutTableHtml Custom PMD XPath 3 No table is used for layout Notes on this rule. Tables
LayoutTableVf Custom PMD XPath 3 No table is used for layout in a Visualforce page Notes on this rule. Tables
MouseEventWithoutKeyboardEventHtml Custom PMD XPath 3 Every function that a mouse reaches, a keyboard reaches too Keyboard
MouseEventWithoutKeyboardEventVf Custom PMD XPath 3 Every function that a mouse reaches in a Visualforce page, a keyboard reaches too Keyboard
NestedScriptElement Custom PMD XPath 3 A script block does not hold the text of another script tag Script Blocks
NoInlineEventHandlerAura Custom PMD XPath 3 An event attribute in Aura markup names a controller action, not a script Lightning Web Components and Aura: Scripts and Resources
NoLightningOutInVisualforce Custom PMD XPath 3 A Visualforce page or component should not use Lightning Out (beta) External Scripts and Stylesheets
VideoWithoutCaptionsHtml Custom PMD XPath 3 A video has a track of captions or subtitles Media
VideoWithoutCaptionsVf Custom PMD XPath 3 A video in a Visualforce page has a track of captions or subtitles Media
@salesforce-ux/slds/enforce-bem-usage Stock Code Analyzer ESLint 4 An SLDS class modifier takes a single underscore, as in slds-button_brand, not a double dash SLDS Classes
@salesforce-ux/slds/enforce-component-hook-naming-convention Stock Code Analyzer ESLint 4 A component styling hook has its current SLDS 2 name Styling Hooks
@salesforce-ux/slds/enforce-sds-to-slds-hooks Stock Code Analyzer ESLint 4 No legacy --sds- styling hook; use the --slds- hook of the same name Styling Hooks
@salesforce-ux/slds/no-hardcoded-values-slds2 Stock Code Analyzer ESLint 4 Colours, spacing, and type sizes come from SLDS 2 styling hooks, not from hard-coded values Styling Hooks
@salesforce-ux/slds/no-slds-class-overrides Stock Code Analyzer ESLint 4 No override of an SLDS class; give the component a class of its own SLDS Classes
@salesforce-ux/slds/no-slds-namespace-for-custom-hooks Stock Code Analyzer ESLint 4 A custom styling hook has a name in the component's own namespace, not in --slds- or --sds- Safe Use of Styling Hooks
@salesforce-ux/slds/no-sldshook-fallback-for-lwctoken Stock Code Analyzer ESLint 4 An --slds- styling hook is never the fallback value of an --lwc- design token Styling Hooks
AvoidIframeVf Custom PMD XPath 4 A Visualforce page does not frame another page Standard Components
FieldsetWithoutLegendHtml Custom PMD XPath 4 A fieldset has a legend that names its group of controls Form Groups, Frames, and Lists
FieldsetWithoutLegendVf Custom PMD XPath 4 A fieldset in a Visualforce page has a legend that names its group of controls Form Groups, Frames, and Lists
FrameWithoutTitleHtml Custom PMD XPath 4 An iframe or a frame has a title that says what it holds Form Groups, Frames, and Lists
FrameWithoutTitleVf Custom PMD XPath 4 An iframe, a frame, or an apex:iframe in a Visualforce page has a title that says what it holds Form Groups, Frames, and Lists
ListItemOutsideListHtml Custom PMD XPath 4 A list item is inside a list: an li in a ul, ol, or menu, and a dt or dd in a dl Form Groups, Frames, and Lists
ListItemOutsideListVf Custom PMD XPath 4 A list item in a Visualforce page is inside a list Form Groups, Frames, and Lists
ObjectWithoutAlternativeHtml Custom PMD XPath 4 An object element holds fallback content Media
ObjectWithoutAlternativeVf Custom PMD XPath 4 An object element in a Visualforce page holds fallback content Media
RepeatedLandmarkWithoutUniqueLabelHtml Custom PMD XPath 4 Landmarks with the same role have different labels Landmarks
RepeatedLandmarkWithoutUniqueLabelVf Custom PMD XPath 4 Landmarks with the same role in a Visualforce page have different labels Landmarks
TableCellHeaderNotFoundHtml Custom PMD XPath 4 A headers attribute names only the id of a th in the same table Notes on this rule. Tables
TableCellHeaderNotFoundVf Custom PMD XPath 4 A headers attribute in a Visualforce page names only the id of a th in the same table Notes on this rule. Tables
TableHeaderWithoutScopeHtml Custom PMD XPath 4 A header cell has a scope, or an id that cells name in their headers attribute Tables
TableHeaderWithoutScopeVf Custom PMD XPath 4 A header cell in a Visualforce page has a scope, or an id that cells name in their headers attribute Tables
TableWithoutCaptionHtml Custom PMD XPath 4 A data table says what it holds, in a caption or an ARIA label Tables
TableWithoutCaptionVf Custom PMD XPath 4 A data table in a Visualforce page says what it holds, in a caption or an ARIA label Tables
TableWithoutHeadersHtml Custom PMD XPath 4 A data table marks its header cells with th Tables
TableWithoutHeadersVf Custom PMD XPath 4 A data table in a Visualforce page marks its header cells with th Tables
VisualforceControllerNaming Custom PMD XPath 4 A custom controller's name ends in Controller, and a controller extension's in Extension or Ext Notes on this rule. General: Controller Names
Metadata Standards: Naming
VisualforceHeaderComment Custom Code Analyzer Regex 4 A Visualforce page or component starts with a comment that says what it is for Comments
DetectCopyPasteForHtml Stock Code Analyzer CPD 5 No copied blocks of HTML markup; share a component Duplicated Markup
DetectCopyPasteForVisualforce Stock Code Analyzer CPD 5 No copied blocks of Visualforce markup; share a component Duplicated Markup
NoUnresolvedTodosMarkup Custom Code Analyzer Regex 5 No TODO, TO-DO, FIXME, FIX-ME, or HACK stays in a markup comment Comments