Appearance
Common Patterns
This guide covers common development patterns and recipes used in LamaPress projects. These patterns solve frequent problems and demonstrate best practices.
Table of Contents
Component Patterns
Pattern 1: Conditional Component Rendering
Only render component if data exists:
php
<?php
$title = $fields['title'] ?? false;
$content = $fields['content'] ?? false;
?>
<?php if ($title || $content): ?>
<div class="ll-block--content">
<?php if ($title): ?>
<h2><?= esc_html($title) ?></h2>
<?php endif; ?>
<?php if ($content): ?>
<div><?= wp_kses_post($content) ?></div>
<?php endif; ?>
</div>
<?php endif; ?>Pattern 2: Looping Through Repeater Fields
Loop through ACF repeater fields:
php
<?php
$items = llField('items', $key) ?? [];
?>
<?php if ($items): ?>
<div class="ll-block--list">
<?php foreach ($items as $item): ?>
<div class="ll-block--list-item">
<h3><?= esc_html($item['title']) ?></h3>
<p><?= wp_kses_post($item['content']) ?></p>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>Pattern 3: Nested Components
Compose components from other components:
php
<?php
$items = $fields['items'] ?? [];
?>
<?php if ($items): ?>
<div class="ll-block--accordion" data-component="blocks/accordion">
<?php foreach ($items as $item): ?>
<?php llBlock('accordion_item', ['fields' => $item]); ?>
<?php endforeach; ?>
</div>
<?php endif; ?>Pattern 4: Image with Fallback
Display image with fallback:
php
<?php
$image = $fields['image'] ?? false;
$fallback = llPublic('images/placeholder.jpg');
$imageUrl = $image ? $image['url'] : $fallback;
$imageAlt = $image ? $image['alt'] : 'Placeholder';
?>
<img src="<?= esc_url($imageUrl) ?>" alt="<?= esc_attr($imageAlt) ?>">Pattern 5: Link Component
Create reusable link component:
php
<?php
$url = $fields['url'] ?? '#';
$text = $fields['text'] ?? 'Click here';
$target = $fields['target'] ?? '_self';
?>
<a href="<?= esc_url($url) ?>" target="<?= esc_attr($target) ?>" class="ll-link">
<?= esc_html($text) ?>
</a>JavaScript Patterns
Pattern 1: Hover Animation
Create hover animations with GSAP:
javascript
import { gsap } from 'gsap/all'
import { killTimeline } from '@src/js/core/utils/helpers'
export default class HoverCard {
constructor(element) {
this.element = element
this.hoverTimeline = null
this.init()
}
init = () => {
this.bindEvents()
}
bindEvents = () => {
this.element.addEventListener('mouseenter', this.handleMouseEnter)
this.element.addEventListener('mouseleave', this.handleMouseLeave)
}
handleMouseEnter = () => {
killTimeline(this.hoverTimeline)
this.hoverTimeline = gsap.timeline()
this.hoverTimeline.to(this.element, {
scale: 1.05,
y: -5,
duration: 0.3,
ease: 'power2.out'
})
}
handleMouseLeave = () => {
killTimeline(this.hoverTimeline)
this.hoverTimeline = gsap.timeline()
this.hoverTimeline.to(this.element, {
scale: 1,
y: 0,
duration: 0.3,
ease: 'power2.out'
})
}
destroy = () => {
killTimeline(this.hoverTimeline)
this.element.removeEventListener('mouseenter', this.handleMouseEnter)
this.element.removeEventListener('mouseleave', this.handleMouseLeave)
}
}Pattern 2: Scroll-Triggered Animation
Reveal animation on scroll:
javascript
import { gsap } from 'gsap/all'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { killTimeline } from '@src/js/core/utils/helpers'
export default class RevealBlock {
constructor(element) {
this.element = element
this.timeline = null
this.init()
}
init = () => {
this.createRevealAnimation()
}
createRevealAnimation = () => {
this.timeline = gsap.timeline({
scrollTrigger: {
trigger: this.element,
start: 'top 80%',
once: true,
}
})
this.timeline.from(this.element, {
opacity: 0,
y: 30,
duration: 0.8,
ease: 'power3.out'
})
}
destroy = () => {
killTimeline(this.timeline)
ScrollTrigger.getAll().forEach(trigger => {
if (trigger.vars.trigger === this.element) {
trigger.kill()
}
})
}
}Pattern 3: Accordion Component
Interactive accordion pattern:
javascript
import { app } from '@src/js/core/app'
import { gsap } from 'gsap/all'
export default class AccordionItem {
constructor(element) {
this.element = element
this.button = this.element.querySelector('.js-accordion-button')
this.content = this.element.querySelector('.js-accordion-content')
this.isOpen = false
this.timeline = null
this.init()
}
init = () => {
this.bindEvents()
this.setInitialState()
}
setInitialState = () => {
gsap.set(this.content, { height: 0, overflow: 'hidden' })
}
bindEvents = () => {
this.button.addEventListener('click', this.toggle)
}
toggle = () => {
if (this.isOpen) {
this.close()
} else {
this.open()
}
}
open = () => {
this.isOpen = true
this.element.classList.add('is-open')
this.timeline = gsap.timeline()
this.timeline.to(this.content, {
height: 'auto',
duration: 0.4,
ease: 'power2.out'
})
}
close = () => {
this.isOpen = false
this.element.classList.remove('is-open')
this.timeline = gsap.timeline()
this.timeline.to(this.content, {
height: 0,
duration: 0.4,
ease: 'power2.in'
})
}
destroy = () => {
this.button.removeEventListener('click', this.toggle)
}
}Pattern 4: Accessing Child Components
Access child component instances:
javascript
import { app } from '@src/js/core/app'
export default class ParentComponent {
constructor(element) {
this.element = element
this.childElements = [...this.element.querySelectorAll('[data-component="blocks/child"]')]
this.init()
}
init = () => {
this.bindEvents()
}
bindEvents = () => {
this.element.addEventListener('click', this.handleClick)
}
handleClick = (e) => {
const childElement = e.target.closest('[data-component="blocks/child"]')
if (childElement) {
const childInstance = app.instances.get('page').instances.get(childElement)
if (childInstance && childInstance.doSomething) {
childInstance.doSomething()
}
}
}
}ACF Patterns
Pattern 1: Conditional Field Display
Show/hide fields based on other fields:
php
<?php
$groupFields = [
[
'key' => $name . '_01',
'label' => 'Show Advanced',
'name' => 'show_advanced',
'type' => 'true_false',
],
[
'key' => $name . '_02',
'label' => 'Advanced Content',
'name' => 'advanced_content',
'type' => 'wysiwyg',
'conditional_logic' => [
[
[
'field' => $name . '_01',
'operator' => '==',
'value' => '1',
],
],
],
],
];Pattern 2: Reusable Field Group
Create reusable field groups:
php
<?php
// In block/part acf.php
$groupFields = [
[
'key' => 'title',
'label' => 'Title',
'name' => 'title',
'type' => 'text',
],
[
'key' => 'content',
'label' => 'Content',
'name' => 'content',
'type' => 'wysiwyg',
],
];Reuse in section:
php
<?php
$reusableFields = llGetFields('block', 'content_block', $key);
$groupFields = array_merge(
$reusableFields,
[
// Additional section-specific fields
]
);Pattern 3: Image Field with Sizes
Use WordPress image sizes:
php
<?php
$image = llField('image', $key);
if ($image) {
$imageUrl = wp_get_attachment_image_url($image['ID'], 'large');
$imageAlt = $image['alt'] ?? '';
}
?>Styling Patterns
Pattern 1: Responsive Container
Container with responsive padding:
php
<div class="ll-container py-4 md:py-8 lg:py-12">
<!-- Content -->
</div>Pattern 2: Grid Layout
Responsive grid layout:
php
<div class="ll-grid">
<div class="col-span-12 md:col-span-6 lg:col-span-4">
<!-- Item 1 -->
</div>
<div class="col-span-12 md:col-span-6 lg:col-span-4">
<!-- Item 2 -->
</div>
<div class="col-span-12 md:col-span-6 lg:col-span-4">
<!-- Item 3 -->
</div>
</div>Pattern 3: Theme Switching
Switch themes based on context:
php
<?php
$theme = $fields['theme'] ?? 'default';
?>
<section class="ll-theme-<?= esc_attr($theme) ?>">
<div class="ll-container">
<!-- Themed content -->
</div>
</section>Animation Patterns
Pattern 1: Stagger Animation
Animate multiple elements with stagger:
javascript
import { gsap } from 'gsap/all'
export default class StaggerList {
constructor(element) {
this.element = element
this.items = [...this.element.querySelectorAll('.js-item')]
this.init()
}
init = () => {
this.createAnimation()
}
createAnimation = () => {
gsap.from(this.items, {
opacity: 0,
y: 20,
duration: 0.6,
stagger: 0.1,
ease: 'power2.out'
})
}
}Pattern 2: Parallax Effect
Simple parallax effect:
javascript
import { gsap } from 'gsap/all'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
export default class ParallaxElement {
constructor(element) {
this.element = element
this.init()
}
init = () => {
gsap.to(this.element, {
yPercent: -50,
ease: 'none',
scrollTrigger: {
trigger: this.element,
start: 'top bottom',
end: 'bottom top',
scrub: true,
}
})
}
}Data Patterns
Pattern 1: Getting Multiple Fields
Get multiple fields efficiently:
php
<?php
$data = llFields('title', 'content', 'image', 'cta_text', $key);
// Returns: ['title' => '...', 'content' => '...', 'image' => '...', 'cta_text' => '...']
?>Pattern 2: Field Mapping
Map field names to different keys:
php
<?php
$data = llFields(
['title' => 'heading'],
['content' => 'body'],
['image' => 'featured_image'],
$key
);
// Returns: ['heading' => '...', 'body' => '...', 'featured_image' => '...']
?>Pattern 3: Options Page Data
Get data from options page:
php
<?php
$companyName = get_field('company_name', 'option');
$address = get_field('address', 'option');
?>Related Documentation
- Component System - Component structure
- JavaScript Architecture - JavaScript patterns
- ACF Integration - ACF patterns
- Styling System - Styling patterns
Next Steps:
- Review Component System for component patterns
- Check JavaScript Architecture for JS patterns
- See Examples & Recipes for more examples